Fixed bug 5241 - SDL on Linux needs a way to turn deadzones off

pj5085

It occurred to me that my simple patch that comments out a few lines of code does not correctly remove the dead zone since the calculation presumably assumes the dead zone has been cut out of the range.  Then, while looking into how to make it output the correct range of values, I realized SDL wasn't returning the correct range of values to begin with.

This line of code was already present:

printf("Values = { %d, %d, %d, %d, %d }\n", absinfo.value, absinfo.minimum, absinfo.maximum, absinfo.fuzz, absinfo.flat);

For my joystick this yeilds:

Values = { 0, -127, 127, 0, 15 }

Then this code calculates the coefficients:

In SDL1:
joystick->hwdata->abs_correct[i].coef[0] = (absinfo.maximum + absinfo.minimum) / 2 - absinfo.flat;
joystick->hwdata->abs_correct[i].coef[1] = (absinfo.maximum + absinfo.minimum) / 2 + absinfo.flat;
t = ((absinfo.maximum - absinfo.minimum) / 2 - 2 * absinfo.flat);
if ( t != 0 ) {
  joystick->hwdata->abs_correct[i].coef[2] = (1 << 29) / t;
} else {
  joystick->hwdata->abs_correct[i].coef[2] = 0;
}

In SDL2:
joystick->hwdata->abs_correct[i].coef[0] = (absinfo.maximum + absinfo.minimum) - 2 * absinfo.flat;
joystick->hwdata->abs_correct[i].coef[1] = (absinfo.maximum + absinfo.minimum) + 2 * absinfo.flat;
t = ((absinfo.maximum - absinfo.minimum) - 4 * absinfo.flat);
if (t != 0) {
  joystick->hwdata->abs_correct[i].coef[2] = (1 << 28) / t;
} else {
  joystick->hwdata->abs_correct[i].coef[2] = 0;
}

Neither calculates the correct coefficients for the code in the AxisCorrect function.

In SDL1:
if ( value > correct->coef[0] ) {
  if ( value < correct->coef[1] ) {
    return 0;
  }
  value -= correct->coef[1];
} else {
  value -= correct->coef[0];
}
value *= correct->coef[2];
value >>= 14;

In SDL2:
value *= 2;
if (value > correct->coef[0]) {
  if (value < correct->coef[1]) {
    return 0;
  }
  value -= correct->coef[1];
} else {
  value -= correct->coef[0];
}

In SDL1, the calculated coefficients are coef[0]=15, coef[1]=-15 and coef[2]=5534751.  So with a full-scale input of 127, it calculates an output value of 37835, which is considerably out of range.

In SDL2, the calculated coefficients are coef[0]=30, coef[1]=-30, and coef[2]=1383687.  So with a full-scale input of 127, it calculates the same output value of 37835.

I tested it with the 3 joysticks I have, and it produces out-of-range values for all of them.

Anyway, since dead zones are garbage, I just deleted all of that junk and wrote some code that takes the absinfo.minimum and absinfo.maximum values and uses them to scale the axis range to -32767 through +32767.

I also made it detect when a range doesn't have an integer center point, e.g. the center of -128 to + 127 is -0.5.  In such cases, if either value to the side of the center is provided, it zeros it, but it otherwise doesn't implement any kind of dead zone.  This seemed important with my gamepad which provides only the values of 0, 127, and 255, since without this hack it would never be centered.

Also, the previous minimum output value was -32768, but as that creates an output range that has no true center, I changed the minimum value to -32767.

I tested it with the 3 joystick devices I have and it seems to create correct values for all of them.
main
Sam Lantinga 2020-12-12 23:48:02 -08:00
parent 0ccb3afd37
commit db0a2025c3
2 changed files with 69 additions and 29 deletions

View File

@ -828,6 +828,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
unsigned long relbit[NBITS(REL_MAX)] = { 0 }; unsigned long relbit[NBITS(REL_MAX)] = { 0 };
unsigned long ffbit[NBITS(FF_MAX)] = { 0 }; unsigned long ffbit[NBITS(FF_MAX)] = { 0 };
SDL_bool use_deadzones = SDL_GetHintBoolean(SDL_HINT_LINUX_JOYSTICK_DEADZONES, SDL_FALSE);
/* See if this device uses the new unified event API */ /* See if this device uses the new unified event API */
if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) && if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) &&
@ -863,7 +864,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
} }
if (test_bit(i, absbit)) { if (test_bit(i, absbit)) {
struct input_absinfo absinfo; struct input_absinfo absinfo;
SDL_bool hint_used = SDL_GetHintBoolean(SDL_HINT_LINUX_JOYSTICK_DEADZONES, SDL_TRUE); struct axis_correct *correct = &joystick->hwdata->abs_correct[i];
if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) { if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) {
continue; continue;
@ -876,20 +877,25 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
#endif /* DEBUG_INPUT_EVENTS */ #endif /* DEBUG_INPUT_EVENTS */
joystick->hwdata->abs_map[i] = joystick->naxes; joystick->hwdata->abs_map[i] = joystick->naxes;
joystick->hwdata->has_abs[i] = SDL_TRUE; joystick->hwdata->has_abs[i] = SDL_TRUE;
if (absinfo.minimum == absinfo.maximum) {
joystick->hwdata->abs_correct[i].used = 0; correct->minimum = absinfo.minimum;
} else { correct->maximum = absinfo.maximum;
joystick->hwdata->abs_correct[i].used = hint_used; if (correct->minimum != correct->maximum) {
joystick->hwdata->abs_correct[i].coef[0] = if (use_deadzones) {
(absinfo.maximum + absinfo.minimum) - 2 * absinfo.flat; correct->use_deadzones = SDL_TRUE;
joystick->hwdata->abs_correct[i].coef[1] = correct->coef[0] = (absinfo.maximum + absinfo.minimum) - 2 * absinfo.flat;
(absinfo.maximum + absinfo.minimum) + 2 * absinfo.flat; correct->coef[1] = (absinfo.maximum + absinfo.minimum) + 2 * absinfo.flat;
t = ((absinfo.maximum - absinfo.minimum) - 4 * absinfo.flat); t = ((absinfo.maximum - absinfo.minimum) - 4 * absinfo.flat);
if (t != 0) { if (t != 0) {
joystick->hwdata->abs_correct[i].coef[2] = correct->coef[2] = (1 << 28) / t;
(1 << 28) / t; } else {
correct->coef[2] = 0;
}
} else { } else {
joystick->hwdata->abs_correct[i].coef[2] = 0; float value_range = (correct->maximum - correct->minimum);
float output_range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
correct->scale = (output_range / value_range);
} }
} }
++joystick->naxes; ++joystick->naxes;
@ -1109,26 +1115,53 @@ AxisCorrect(SDL_Joystick *joystick, int which, int value)
struct axis_correct *correct; struct axis_correct *correct;
correct = &joystick->hwdata->abs_correct[which]; correct = &joystick->hwdata->abs_correct[which];
if (correct->used) { if (correct->minimum != correct->maximum) {
value *= 2; if (correct->use_deadzones) {
if (value > correct->coef[0]) { value *= 2;
if (value < correct->coef[1]) { if (value > correct->coef[0]) {
return 0; if (value < correct->coef[1]) {
return 0;
}
value -= correct->coef[1];
} else {
value -= correct->coef[0];
} }
value -= correct->coef[1]; value *= correct->coef[2];
value >>= 13;
} else { } else {
value -= correct->coef[0]; int new_value = (int)SDL_floorf((value - correct->minimum) * correct->scale + SDL_JOYSTICK_AXIS_MIN + 0.5f);
/* At least one gamepad has a minimum of 0 and a maximum is 255,
* which makes it's center point a non-integer, 127.5. Thus it
* must report either 127 or 128, neither of which is center.
* At least one analog controller with the same range returns 128
* easily when at center, but seems completely incapable of
* returning 127 no matter how carefully the stick is moved.
* So here we detect if the axis' range does not have a center
* point, and if so, and if the reported position is on either
* side of center, we report that it is actually at the center.
*/
if ((correct->maximum - correct->minimum) & 1) {
int lower_center = (correct->maximum - correct->minimum - 1) / 2;
int higher_center = (correct->maximum - correct->minimum + 1) / 2;
if (value == lower_center || value == higher_center) {
new_value = 0;
}
}
value = new_value;
} }
value *= correct->coef[2];
value >>= 13;
} }
/* Clamp and return */ /* Clamp and return */
if (value < -32768) if (value < SDL_JOYSTICK_AXIS_MIN) {
return -32768; return SDL_JOYSTICK_AXIS_MIN;
if (value > 32767) }
return 32767; if (value > SDL_JOYSTICK_AXIS_MAX) {
return SDL_JOYSTICK_AXIS_MAX;
}
return value; return value;
} }

View File

@ -58,8 +58,15 @@ struct joystick_hwdata
struct axis_correct struct axis_correct
{ {
int used; SDL_bool use_deadzones;
/* Deadzone coefficients */
int coef[3]; int coef[3];
/* Raw coordinate scale */
int minimum;
int maximum;
float scale;
} abs_correct[ABS_MAX]; } abs_correct[ABS_MAX];
SDL_bool fresh; SDL_bool fresh;