PascalGameDevelopment/SDL2-for-Pascal

Game controller D-Pad is handled incorrectly

Closed this issue · 11 comments

I have a problem with handling my controller using SDL. It is about such a controller:

retro-bit pc usb controller

As you can see, it has a D-Pad (two axes) and four buttons. Windows 10 detects it correctly, everything works normally:

windows set up controllers

SDL detects this controller correctly, but when I press the D-Pad's arrows, it generates much more events than it should. Pressing the left arrow sends eight events to four axes (two for each axis), even though the controller only has two. Also, the SDL_JoystickNumAxes function in this case returns 5, which is also invalid (it should return 2).

In the SDL_Init function I pass the SDL_INIT_JOYSTICK flag and the initialization is successful. I connect the controller to the USB port, SDL sends the SDL_JOYDEVICEADDED event, so I call the SDL_JoystickOpen function — it opens the joystick correctly. Then I read the provided data in the SDL_JOYAXISMOTION event handler.

I wrote the debug code. After receiving the SDL_JOYAXISMOTION event, I display the data provided in the event to the console. The code is simple, it looks like this:

  case AEvent.Type_ of
    {..}
    SDL_JOYAXISMOTION:
    begin
      WriteLn('Event: SDL_JOYAXISMOTION');
      WriteLn('Type:  ', AEvent.JAxis.Type_);
      WriteLn('Axis:  ', AEvent.JAxis.Axis);
      WriteLn('Value: ', AEvent.JAxis.Value);
      WriteLn();
    end;
  end;

Pressing and holding the left arrow on D-Pad produces this:

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  0
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  0
Value: -32768

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  1
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  1
Value: -32768

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  2
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  2
Value: -32768

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  3
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  3
Value: -32768

Left arrow released:

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  0
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  1
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  2
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  3
Value: -256

Pressing and releasing up arrow:

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  4
Value: -256

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  4
Value: -32768

Event: SDL_JOYAXISMOTION
Type:  1536
Axis:  4
Value: -256

And so on. I checked my code on two other cheap (Chinese) controllers and those are properly handled by SDL (and Windows 10 too), but retro-bit is not. Although as I wrote — Windows has no problem with all my controllers, various emulators also have no problems (Mesen, FCEUX etc.), but SDL does with one.

I'm going to update the joystick.inc, this may help. Please be patient.

Please try out if it works with the updated joystick.inc.

There are a lot of new functions to see, if your device is detected correctly, if it doesn't work. Please check them. E.g. SDL_JoystickName and SDL_JoystickGetType (should return SDL_JOYSTICK_TYPE_GAMECONTROLLER).

Please let us know, if it works or not :-).

I upaded the joystick header but the situation is the same as earlier — SDL creates many SDL_JOYAXISMOTION events when I pressed the D-Pad arrow.

The joystick is properly detected and opened, the SDL_JoystickGetType function returns SDL_JOYSTICK_TYPE_GAMECONTROLLER and the SDL_JoystickName function returns USB Gamepad — the same as controllers tool in Windows. I used the following code to test controller:

{$MODE OBJFPC}{$LONGSTRINGS ON}

uses
  SDL2;
var
  Window: PSDL_Window;
  Renderer: PSDL_Renderer;
  Joystick: PSDL_Joystick;
  Event: TSDL_Event;
var
  Stopped: Boolean = False;
begin
  if SDL_Init(SDL_INIT_EVERYTHING) < 0 then Halt;

  Window := SDL_CreateWindow('test', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
  if Window = nil then Halt;

  Renderer := SDL_CreateRenderer(Window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_TARGETTEXTURE);
  if Renderer = nil then Halt;

  while not Stopped do
  begin
    while SDL_PollEvent(@Event) = 1 do
    case Event.Type_ of
      SDL_JOYDEVICEADDED:
      begin
        Joystick := SDL_JoystickOpen(Event.JDevice.Which);

        WriteLn('Event:   SDL_JOYDEVICEADDED');
        WriteLn('Type:    ', SDL_JoystickGetType(Joystick));
        WriteLn('Name:    ', SDL_JoystickName(Joystick));
        WriteLn('Axes:    ', SDL_JoystickNumAxes(Joystick));
        WriteLn('Buttons: ', SDL_JoystickNumButtons(Joystick), LineEnding);
      end;
      SDL_JOYAXISMOTION:
      begin
        WriteLn('Event: SDL_JOYAXISMOTION');
        WriteLn('Type:  ', Event.JAxis.Type_);
        WriteLn('Axis:  ', Event.JAxis.Axis);
        WriteLn('Value: ', Event.JAxis.Value, LineEnding);
      end;
      SDL_QUITEV: Stopped := True;
    end;

    SDL_Delay(20);
  end;

  {...}
end.

When I run the program and connect retro-bit controller, this will appear in the console:

Event:   SDL_JOYDEVICEADDED
Type:    1
Name:    USB Gamepad
Axes:    5
Buttons: 10

Axes count is invalid — should be 2. Buttons count is correct, because this retro-bit controller has a PCB with support to 10 buttons (to match universal button codes standard). Maybe the axes count should be 5 in the case of this controller, I don't know, but this is not the problem — the main problem is creating many events, which makes this controller impossible to handle from SDL.

In my main project I'm not using events to check input. In this project, SDL_JoystickGetButton is used to check button states and this function returns correct values. To determine D-Pad state, I'm using SDL_JoystickGetAxis function and it returns wrong values for this retro-bit controller. And again, there are no problems with my other controllers, the retro-bit causes problems only.

So no matter which way I will use (events or functions), I'm not able to handle this controller using SDL.

I see, and feared this could be the result.

What happens if you poll for SDL_CONTROLLERDEVICEADDED and SDL_CONTROLLERAXISMOTION instead of their joystick analogues? Are these triggered, too? Do they behave the same?

I checked what it looks like in the case of PSDL_GameController with the following code:

{$MODE OBJFPC}{$LONGSTRINGS ON}

uses
  SDL2;
var
  Window: PSDL_Window;
  Renderer: PSDL_Renderer;
  Controller: PSDL_GameController;
  Event: TSDL_Event;
var
  Stopped: Boolean = False;
begin
  if SDL_Init(SDL_INIT_EVERYTHING) < 0 then Halt;

  Window := SDL_CreateWindow('test', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
  if Window = nil then Halt;

  Renderer := SDL_CreateRenderer(Window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_TARGETTEXTURE);
  if Renderer = nil then Halt;

  while not Stopped do
  begin
    while SDL_PollEvent(@Event) = 1 do
    case Event.Type_ of
      SDL_CONTROLLERDEVICEADDED:
      begin
        Controller := SDL_GameControllerOpen(Event.CDevice.Which);

        WriteLn('Event: SDL_JOYDEVICEADDED');
        WriteLn('Error: "', SDL_GetError(), '"');
      end;
      SDL_CONTROLLERAXISMOTION:
      begin
        WriteLn('Event: SDL_CONTROLLERAXISMOTION');
        WriteLn('Type:  ', Event.CAxis.Type_);
        WriteLn('Axis:  ', Event.CAxis.Axis);
        WriteLn('Value: ', Event.CAxis.Value, LineEnding);
      end;
      SDL_CONTROLLERBUTTONDOWN:
      begin
        WriteLn('Event: SDL_CONTROLLERBUTTONDOWN');
        WriteLn('Button ', Event.CButton.Button, ' state: ', Event.CButton.State, LineEnding);
      end;
      SDL_CONTROLLERBUTTONUP:
      begin
        WriteLn('Event: SDL_CONTROLLERBUTTONUP');
        WriteLn('Button ', Event.CButton.Button, ' state: ', Event.CButton.State, LineEnding);
      end;
      SDL_QUITEV: Stopped := True;
    end;

    SDL_Delay(20);
  end;

  {...}
end.

The SDL_CONTROLLERDEVICEADDED is fired when I connect this controller, opening it using SDL_GameControllerOpen returns correct pointer and no error. The problem is that SDL_CONTROLLERAXISMOTION event is never generated. When I press D-Pad arrows, SDL generates many SDL_CONTROLLERBUTTONDOWN events instead, for non-existent buttons.

For example, if i press and release Left arrow, the console shows this:

Event: SDL_CONTROLLERBUTTONUP
Button 13 state: 0

Event: SDL_CONTROLLERBUTTONDOWN
Button 13 state: 1

Event: SDL_CONTROLLERBUTTONUP
Button 13 state: 0

For Right arrayw this:

Event: SDL_CONTROLLERBUTTONUP
Button 13 state: 0

Event: SDL_CONTROLLERBUTTONDOWN
Button 14 state: 1

Event: SDL_CONTROLLERBUTTONUP
Button 14 state: 0

Event: SDL_CONTROLLERBUTTONUP
Button 13 state: 0

For Up arrow:

Event: SDL_CONTROLLERBUTTONDOWN
Button 11 state: 1

Event: SDL_CONTROLLERBUTTONUP
Button 11 state: 0

And for Down arrow:

Event: SDL_CONTROLLERBUTTONUP
Button 11 state: 0

Event: SDL_CONTROLLERBUTTONDOWN
Button 12 state: 1

Event: SDL_CONTROLLERBUTTONUP
Button 12 state: 0

Event: SDL_CONTROLLERBUTTONUP
Button 11 state: 0

So those events are crazy. And the strenge thing is that the first arrow press after connecting this controller allways produces SDL_CONTROLLERBUTTONUP event at first. This you can see in the first console output sample in this post. It seems that SDL is reading the number of axes incorrectly and in addition, it treats these axes as buttons by coding them after the codes of the basic buttons.

There is also a second problem — in this test snippet, the button codes do not match what the system utility shows.

Button name Windows code SDL code
Select 8 4
Start 9 6
B 2 0
A 1 1

Completely messed up.

suve commented

Can you try writing a similar program in C and testing if it experiences the same issue? (If you're not familiar with C, let me know and I can help you out.) That would allow us to determine if the issue is actually caused by our Pascal units, or if it's a bug in SDL2 itself.

It will be faster when you provide me with an exe to check the operation of the controllers. I haven't written for years in C/C++ and I don't have any tools for it. I would like to support this controller, because many of my ”customers” use it and complain that they will not be able to use it.

I don't think this is a Pascal headers bug, it is most likely a SDL2 bug. This controller is a Chinese product, so I don't expect it to be very high quality either. But the weird thing is that Windows has no problem with its operation (no problem with handling it using joyGetPosEx), and this suggests that the controller itself is not defective.

It may not even be a bug but just that the controller is not recognized correctly.

See this dicussion. - Especially the last two paragraphs of the last reply are intersting. The mapping may be unknown to SDL2, so you could provide an own mapping.

And that's why I don't want to use PSDL_GameController and all this wrappers, because I don't need the mapping functionality at all. As written in this article, the functions to support PSDL_Joystick are a low-level API to support all kinds of input devices, not just game controllers in the from of Xbox-like controllers.

SDL should correctly recognize each input device and generate the appropriate SDL_JOY* events, and if events for game controllers are enabled, it should also generate SDL_GAMECONTROLLER* events filled with the values according to obtained mapping.

For now, it looks like SDL is trying hard to make the user happy (a programmer), mapping the buttons of the connected device, although it has no idea what the device is. As a result, all events contain erroneous data. That is why Windows has no problem with support this controller, but SDL produces bullshit events.

I'm pissed off of this. I don't like when an API treats a programmer like an idiot and does something the developer should do. Even more so, considering that there are thousands of controllers on the market, each of them has a different number of axes and buttons and their different layout, so it is impossible to write a correctly working mapping code for each possible device. It's foolish to try to do this, to the detriment of programmers and their customers.

All I need is a low-level API to handle any input devices, not just game controllers. The mapping code, if I want to, I will write myself.

Hi! It's been a long time since this bug was reported, but new information has come out. More and more people have been touched by the problem with incorrect handling of controllers by SDL, the problem concerns not one or at least two models popular on the Chinese controller market. In all these cases, the problem is the same — SDL generates multiple axis events when the button of only one axis is pressed.

We have looked deeper into this matter and it turns out that the problem is not SDL, but an incorrectly designed and made electronic circuit in these controllers. These controllers completely wrongly present themselves as having two analog sticks instead of one digital axis (D-Pad). We used the Gamepad Tester tool to test the operation of various controllers.

My controller — Chinese NES-like Data Frog — that works fine in SDL is detected like this:

image

As you can see, the X and Y axes are digital (two-state) because they are represented by a physical D-Pad. In the case of a retro-bit controller, it looks like this:

image

Even though this controller also only has a D-Pad and four buttons, the software detects it as a controller having two analog sticks and no D-Pad (and supports standard mapping, instead of supporting none). Most likely, the manufacturer of these controllers uses the PCBs of these controllers also for other products with a different number and type of axes/buttons. Not only does this controller cause problems with my game, but also a few others where base axes cannot be mapped (using the game's settings screen). One of the programs is the Mesen emulator.

So close this bug report because it is invalid — SDL is working fine and the problem is poor quality controllers.
Sorry to mislead you.

Thanks for your highly interesting report on this!

I'm glad though, it is not our translation project :-D.