Sunday, June 14, 2009

Out of sync and upside down: Windows mouse pointer acceleration (Enhance pointer precision) is partially broken

Windows mouse handling ("Enhance pointer precision" option) has always been partially broken in Vista and XP due to a simple design error which is only now fixed in Windows 7.

The "Enhance pointer precision" code uses a calculation that should have included:
× ScreenResolution(DPI) ÷ MouseBusUpdateRate.
The (incorrect) calculation actually used is:
× ScreenRefreshRate ÷ ScreenResolution(DPI).
"Out of sync and upside down."

The Enhance pointer precision checkbox

Windows has long provided a "mouse pointer acceleration" feature that determines how fast the on-screen mouse pointer (cursor) moves in response to movements of the mouse.
(In Windows XP and Windows Vista, mouse pointer acceleration is turned on using the Control Panel>Mouse>Pointer Options>"Enhance pointer precision" checkbox.)

If acceleration is enabled, when the mouse moves slowly, the pointer is moved at a lower ratio, for example 1 count ('dot') of mouse movement might correspond to 1 pixel or less of pointer movement. But if the mouse moves faster, the system responds by accelerating the movement of the pointer, using a higher ratio, for example 1 count of mouse movement then might correspond to 2 or more pixels of pointer movement.

Starting with Windows XP and continuing through Windows Vista and onto Windows 7 RC1, Windows implements mouse pointer acceleration in a different way than in previous versions of Windows.

The new implementation made important improvements in how acceleration works, and in most cases works well enough.
Some (including myself) consider it essential to enable mouse pointer acceleration [link to Coding Horror blog].

HOWEVER, there is a simple conceptual error in its design, which has been carried into the implementation.

There is a large possibility that the effects of the error were found during testing and an attempt was made to fix it.
BUT that fix did not correct the error, and instead merely masked its effects AND INVERTED and UNDID the intended behaviour.

RIGHT NOW, for millions of people, movement of the mouse pointer on their screens is being controlled by an algorithm which includes that conceptual error and "fix error".

The broken behaviour affects users running Windows XP or Windows Vista.
It was present in the first release of Windows XP in October 2001 and continues through Windows Vista SP2.
The conceptual error and the "fix error" have finally been fixed in Windows 7.

What is the broken behaviour?

The broken behaviour can be very very subtle, which may explain why it has not been more widely recognized as a problem and why it has taken so long for it to be fixed. The behaviour is:

  • How far the on-screen mouse pointer moves in response to on-mouse-pad mouse movement depends on the monitor refresh rate (higher refresh rates cause larger pointer movements). But the monitor refresh rate should have no effect.
  • Increasing the display DPI setting (Large size vs. Normal size in the Control Panel) causes the mouse pointer to move fewer on-screen pixels in response to on-mouse-pad mouse movement. But an increased DPI setting should cause the pointer to move a larger number of on-screen pixels, not fewer.

How should it work?

By reading what Microsoft have written about Windows XP mouse acceleration, and some investigation, we can explain how the acceleration should have worked.

Microsoft intended that the velocity (speed) of the on-screen pointer (measured in inches per second) would depend upon the velocity of the on-mouse-pad mouse (also measured in inches per second).

They defined an acceleration formula that translates the velocity of the on-mouse-pad mouse to the corresponding velocity of the on-screen pointer.

The algorithm works like so:

  1. During a time period, measure the count of mouse movements (also known as 'dots' or Mickeys).
  2. Convert the mouse movement count into a mouse velocity.
  3. Using the acceleration formula, calculate the corresponding pointer velocity.
  4. Convert the pointer velocity into a number of pixels of pointer movement.

Step 4 is where the conceptual error was made and also the "fix error".

To turn the calculated pointer velocity into a number of pixels of mouse movement involves some simple formulae.

Velocity = Distance

If the "Time" term corresponds to a regular periodic interval, such as the time period between mouse updates, or the time period between each screen refresh, then we can also use this:

Rate = 1

For example, a regular time period of 0.2 seconds corresponds to a rate of 5 times a second (5 Hz).

Combining the two formulae, we get:

Velocity = Distance × Rate

If something is moving 2 inches every 0.2 seconds (5 Hz) we can use the Velocity = Distance/Time formula to calculate Velocity=10 inch/s, OR we can use the Velocity=Distance×Rate formula to also calculate 10 inch/s.

In the case of moving the mouse pointer, what is the appropriate "Time" and "Rate" to use?

This is a key question, and where the conceptual error occured.
The correct time interval is the interval that occurs between successive pointer position updates and (identically) the correct rate is the rate at which the pointer position is updated.
In Windows, the mouse position is updated every time mouse movement counts are sent from the mouse to the PC.
The correct rate to use is the Mouse Bus Update Rate.
A moving mouse connected via a PS/2 bus typically sends movement counts 100 times each second (100 Hz).
A moving mouse connected via a USB bus typically sends movement counts 125 times each second (125 Hz).

How to measure "Distance" for the on-screen pointer?

Windows stores and uses an assumed screen resolution, in Dots Per Inch (DPI) in the Control Panel>Display Properties>Settings>Advanced dialog.
That Screen Resolution (DPI) can be used like so:

DistancePointer = PixelsPointer

Putting all of the formula together, we get this formula for pointer velocity:

VPointer = PixelsMouseBusUpdate × MouseBusUpdateRate

The formula above is the formula that SHOULD have been used.
At this point, the team within Microsoft designing and implementing the mouse acceleration got confused.
This confusion is documented in Microsoft's website here:
Pointer Ballistics for Windows XP

The section titled "Relating to Physical Units" indicates that an incorrect formula for Vpointer was used:

VPointer = PixelsMouseBusUpdate × ScreenUpdateRate

Compared to the correct formula, note that ScreenUpdateRate is used where MouseBusUpdateRate should have been used instead.

Using ScreenUpdateRate in the formula would have been appropriate IF the pointer position was updated every screen refresh.
HOWEVER, the pointer position IS NOT updated every screen refresh; it is updated on every mouse bus update.

Of course, it could have been that the Microsoft team had only been imprecise with their formula.
HOWEVER, the example in that section also shows that they have used their incorrect formula: a correct examination of their example gives a physical to virtual gain of 5, NOT the 3 that they calculate.

Of course, it could have been that the Microsoft team had only been imprecise with their formula, AND also clumsy with their example.
HOWEVER, examination of the actual mouse behaviour in Windows XP and Windows Vista shows that they DO have the incorrect ScreenUpdateRate term in their algorithm, AND WORSE!

Considering again the correct formula for Vpointer and rearranging and solving for Pixels:

PixelsMouseBusUpdate = VPointer × ScreenResolution

Let's examine this formula.
Suppose a monitor having a high resolution (a high DPI, in other words, a small pixel size/pitch).
In order to move the mouse pointer a given physical distance on this high resolution monitor (measured in inches), we would have to move the pointer a LARGER number of pixels, because each pixel is smaller. Note that the formula above MULTIPLIES by ScreenResolution, which will have the desired effect of calculating a LARGER number of SMALLER pixels.

What formula is used by Windows XP and Windows Vista?

Despite the confused formula and example on the Microsoft Pointer Ballistics webpage, one would hope that the correct PixelsMouseBusUpdate formula is used, as above.
One might suspect that a formula that had ScreenUpdateRate instead of MouseBusUpdateRate is used.
However, the formula used is:

PixelsMouseBusUpdate = VPointer × ScreenUpdateRate

Note: rather than multiplying by ScreenResolution, the formula divides by ScreenResolution.
Rather than divide by ScreenUpdateRate (or MouseBusUpdateRate) the formula instead multiplies by ScreenUpdateRate.

Consider again the monitor having a high resolution (a small pixel size/pitch).
Rather than compensating for the small pixel size, the formula used amplifies the effect of a small pixel size: For a high resolution monitor, Windows XP and Windows Vista move the mouse pointer a SMALLER number of SMALLER pixels which is undesirable.

Also undesirable is that how far and how fast Windows XP and Windows Vista move the mouse pointer, depends upon the screen refresh rate.
(The Microsoft team put screen refresh rate in their formula because they wanted to REMOVE any possible effect that screen refresh rate might have had on mouse pointer movement, but instead they ADDED an undesirable effect.)

How could this problem have remained unfixed for so long?

Consider a typical monitor with a refresh rate of 75 Hz and 96 DPI, and a typical mouse with a USB bus update rate of 125 Hz.
The correct PixelsMouseBusUpdate formula calculates ScreenResolution/MouseBusUpdateRate = 96/125 = 0.768.
The "out of sync and upside down" formula that Windows XP and Windows Vista use calculates ScreenUpdateRate/ScreenResolution = 75/96 = 0.78125.
These two numbers differ by less than 2%.

Might the Microsoft team have jumped directly to the incorrect ScreenUpdateRate ÷ ScreenResolution calculation and then not realised the mistake?

Might they have started with ScreenResolution ÷ ScreenUpdateRate and realised something was wrong and then "fixed" it by inverting the numerator and divisor?

We may never know.

A silly flash toy...

The real reason why mouse acceleration is broken? [link]