Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Effect corrections part2 #107

Merged
merged 8 commits into from
Jul 13, 2024
Merged

Effect corrections part2 #107

merged 8 commits into from
Jul 13, 2024

Conversation

MmAaXx500
Copy link
Contributor

@MmAaXx500 MmAaXx500 commented Jun 23, 2024

Part1: #105

Updated the documentation that I forgot in part1, all the indicated values are sent by the Windows driver.
Same for part2.

  • Gain (no changes required)
  • Autocenter (found no way to adjust that through Direct Input, only a GUI slider is available in the driver, based on that seems to be ok)
  • Spring
  • Damper, Friction, Inertia

- unified handling of all condition effects for uploads and updates
- added new saturation parameters
- added right, left, and right&left update type for saturation
- added right&left update type for coefficient
@MmAaXx500
Copy link
Contributor Author

Condition effects required more changes than I expected, there are 4 new fields and 4 new messages that I added to the documentation and implemented in the driver.

Summary of what Windows and what this PR uses:

Windows This PR
Coefficient [-32767; 32764] [-32767; 32767]
Deadband [-32767; 32767] [-32767; 32767]
Saturation (damper) [3; 32764] [0; 32764]
Saturation (spring) [0; 27302] [0; 27302]

Nothing outstanding come in my mind about the changes, if something is not clear I'll clarify it.

Spreadsheet: ffb.zip (had to be compressed to fit)
Windows test program: ffbtest_win.cpp.txt
Linux test program: ffbtest_linux.cpp.txt

@MmAaXx500 MmAaXx500 marked this pull request as ready for review June 25, 2024 19:00
@peterlopen
Copy link

thanks for your work, this looks great.
I checked charts and since there are some differences and between win, linux and wine I am wondering if those are before your changes were applied? and those corr values are values with your changes?
since corr values are missing in most charts I am wondering what else is missing.
thank you.

@MmAaXx500
Copy link
Contributor Author

I checked charts and since there are some differences and between win, linux and wine I am wondering if those are before your changes were applied?

On the charts, the X axis (horizontal) shows the OS specific API input value and the values on the Y axis (vertical) shows the value that was sent to the wheel. The X and Y values should match between Windows and Wine if the driver is working correctly, while the Linux values should match only on the Y axis exactly and be proportional on the X axis.

and those corr values are values with your changes?

Yes, the corr (corrected) columns and lines are generated using this PR applied.

since corr values are missing in most charts I am wondering what else is missing.

I'm not aware of any missing values, I think you just need to scroll down a fair bit to see them. There are about 65000 rows on each sheet.

@peterlopen
Copy link

Thanks for explanation.
Will check that with a wheel some time 😀

@Kimplul
Copy link
Owner

Kimplul commented Jun 27, 2024

Kind of interesting that the FEdit program I used to do most of the reverse engineering never even touched the saturation.

I had a cursory glance at code, looks pretty good to me. I'll play around with it over the weekend, test out some games and have a closer look at the details before merging. In any case, really nice work, well done.

@MmAaXx500
Copy link
Contributor Author

Kind of interesting that the FEdit program I used to do most of the reverse engineering never even touched the saturation.

I had tried FEdit, but I was unable to get it to work reliably under my Win 10 vm, so I have no experience with it. Maybe they forgot to implement it. The saturation fields of the DICONDITION structure of dinput was unchanged from at least DirectX 5.


I think I found something (apart from a missing title). There are 33 possible update packets. The type of the update is coded in the 4th and 5th bytes as bit fields.

Do you want to have them all implemented? For me, it seems to be little benefit compared to the implementation complexity if you are not aware of any issue that caused by that these are not implemented. This PR in the current state already reduces the worst case of 5 update packets to 3 (0e 43 and 0e 70 are implemented).

Here is the table as context (same in the docs)

4th byte 5th byte   hex
00001110 01000001  0e 41  positive coefficient
00001110 01000010  0e 42  negative coefficient
00001110 01000011  0e 43  both     coefficint
00001110 01001100  0e 4c  right and left deadband
00001110 01010000  0e 50  positive saturation
00001110 01100000  0e 60  negative saturation
00001110 01110000  0e 70  both     saturation
00001110 01001111  0e 4f  coefficient + deadband
00001110 01111100  0e 7c  deadband    + saturation
00001110 01110011  0e 73  coefficient + saturation
00001110 01101101  0e 6d  positive coefficient + deadband + negative saturation
00001100           0c     everything

The original reason why I started to write this comment, to document here that these type of packets can be discovered by updating multiple values with one call of SetParameters. The other way is to issue these calls in short succession because the Windows driver merges updates that are happened in a short timeframe.

@MmAaXx500
Copy link
Contributor Author

Came up with a simpler implementation than I thought before.
Made it as a separate commit to be easily removable if it is not desired.

It can be made even simpler if we only use the 0x0e message type, which results in losing the equivalent behavior with the Windows driver and adds an extra byte to the message. (0x0e can include 6 values, not just 5)

@Kimplul
Copy link
Owner

Kimplul commented Jun 29, 2024

It can be made even simpler if we only use the 0x0e message type, which results in losing the equivalent behavior with the Windows driver and adds an extra byte to the message. (0x0e can include 6 values, not just 5)

Would it be easier to just dump all parameters in a single message if one/multiple of them has changed? I don't necessarily think we have any massive bandwidth limitations, but sending fewer packets with more data is probably preferable over sending multiple smaller packets, and minimizing branches tends to make code easier to read. I don't think the wheel itself has significant issues dealing with a bit of 'unnecessary' data in the packets, but that should of course be tested.

When I started this project, before I had done any proper USB reverse engineering, I was sure that update packets would just dump the new effect state as a whole because that just seemed like the sensible thing to do, and was sorely disappointed when the tools I was using seemed to just send a single parameter per packet. Looking at the data with new eyes, it seems like at least the periodic effect also uses a bitfield, and might also benefit from merging updates.

The order in which fields are populated is a bit weird, I would've expected the packet to have fixed slots for each parameter, but apparently they act as a kind of priority stack?

@MmAaXx500
Copy link
Contributor Author

Would it be easier to just dump all parameters in a single message if one/multiple of them has changed?

That's a good idea! I have just checked if this has any side effects like stutter or something on the not changed parameters, but I don't feel any, so it's probably safe to do that.

Looking at the data with new eyes, it seems like at least the periodic effect also uses a bitfield, and might also benefit from merging updates.

Yes, there is! It's 0e 0f. It's going to be a separate PR to don't mix them.

The order in which fields are populated is a bit weird, I would've expected the packet to have fixed slots for each parameter, but apparently they act as a kind of priority stack?

Yes, the order is fixed, the location in the packet is not. Probably easier for them to parse that way.

@MmAaXx500
Copy link
Contributor Author

I saw some packets that merged duration update with the other parameters, I will check it tomorrow.

@MmAaXx500
Copy link
Contributor Author

I'm currently wondering about why does the duration updated with every update request, even it is unchanged.

I saw 0e47624 that led me to #43. There is a comment from berarma that states:

Updating an effect with the same duration is still an update and it should make the effect last longer since the length counter is reset again to its initial value.

I don't know what is expected under Linux or how other wheels behave, but SDL does not do any special calculations neither on Windows [1] nor Linux [2] while handling uploads [3,4] or updates [5,6].

The documentation for Direct Input states:

If the effect is playing while the parameters are changed, the new parameters take effect as if they were the parameters when the effect started.

For example, suppose a periodic effect with a duration of three seconds is started. After two seconds, the direction of the effect is changed. The effect then continues for one additional second in the new direction. The envelope, phase, amplitude, and other parameters of the effect continue smoothly, as if the direction had not changed.

In the same situation, if after two seconds the duration of the effect were changed to 1.5 seconds, the effect would stop.

The wheel's behavior matches the Direct Input documentation: when a 10-second effect is uploaded and then updated to a 2-second effect after 5 seconds, it stops immediately instead of playing for an additional 2 seconds.

What is your opinion about this? Can we include the duration update in the new 4c update packet together with the other values, or you want to keep the current behavior?


[1]: windows conversion: https://github.com/libsdl-org/SDL/blob/main/src/haptic/windows/SDL_dinputhaptic.c#L656
[2]: linux conversion: https://github.com/libsdl-org/SDL/blob/main/src/haptic/linux/SDL_syshaptic.c#L744
[3]: windows upload: https://github.com/libsdl-org/SDL/blob/main/src/haptic/windows/SDL_dinputhaptic.c#L933
[4]: linux upload: https://github.com/libsdl-org/SDL/blob/main/src/haptic/linux/SDL_syshaptic.c#L935
[5]: windows update: https://github.com/libsdl-org/SDL/blob/main/src/haptic/windows/SDL_dinputhaptic.c#L961
[6]: linux update: https://github.com/libsdl-org/SDL/blob/main/src/haptic/linux/SDL_syshaptic.c#L968

@Kimplul
Copy link
Owner

Kimplul commented Jun 30, 2024

I'm currently wondering about why does the duration updated with every update request, even it is unchanged

Heh, now I'm a bit embarrassed. I thought that re-sending the duration would reset the length counter, but I just tried it out again and you're right, the effect stops once the internal length counter reaches the specified duration. I thought I tested it, but I must've done a really poor job of it. But the idea was to extend the effect duration for each update.

What is your opinion about this? Can we include the duration update in the new 4c update packet together with the other values, or you want to keep the current behavior?

If Logitech wheels extend the duration, I suppose we should too. Just call it a de-facto standard ;)

I'm still a bit annoyed how poorly documented this aspect of the Linux FFB subsystem is, I could easily see both approaches being the more 'intuitive' option.

In addition to the SDL sources you linked, Wine doesn't seem to do any adjustments to the duration, possibly indicating that Wine assumes that the DInput duration handling would also apply to Linux: https://gitlab.winehq.org/wine/wine/-/blob/master/dlls/dinput/joystick_hid.c?ref_type=heads#L2482 (kind of hard to link to code that doesn't exists, but you know)

On the other hand, the driver for memoryless wheels seems to treat an upload as a duration extension (as do Logitech wheels, don't know if there's some ancestry there): https://elixir.bootlin.com/linux/latest/source/drivers/input/ff-memless.c#L462

The memoryless driver could arguably be seen as the source of truth for how wheels should behave on Linux, as the driver from what I can tell is the earliest FFB anything in the kernel. Which would mean that SDL and Wine are buggy, I guess?

Scratch that, I got them reversed, hid-lg(ff/whatever).c is older than ff-memless.c so the Logitech wheels treating an update as a duration extension is the 'original' behaviour, as far as I can tell. Assuming we treat it as the One True Behaviour on Linux, SDL and Wine are still buggy I guess?

It might not even be that significant, I can't remember seeing any games that would require one or the other, generally most games just set effects to be of infinite length and just send a STOP signal when they should end.

@MmAaXx500
Copy link
Contributor Author

I have taken a look at some projects to see what they are doing.
The Fanatec driver works with the same logic as Windows does, it doesn't extend the duration. Stop time is calculated from the start time, which is set only at the start of the effect.

To make the things more interesting, the in kernel hid-pidff doesn't even allow sending an update with the same duration parameter. Depending on the interpretation, the effect duration cannot be doubled, or it expects the Windows way of doing it.
The OpenFFBoard project using this driver (based on this comment) and they are doing it in the Windows way. Duration is updated directly without any calculations and the start time is only updated at the start of the effect.

(I hope I read them correctly)

Out of curiosity, do you know any driver that does the ff-memless way of the duration update and does not depend on ff-memless?

I'm still a bit annoyed how poorly documented this aspect of the Linux FFB subsystem is

I feel it :)

I'm in favor of keeping the current (Windows) behavior that maybe incorrect, but it does not break SDL, Wine, and probably more programs that expect the Windows behavior.

Whatever is the decision is going to be, I want to push a commit to use the 4c packet for the duration updates too before this gets merged.

@Kimplul
Copy link
Owner

Kimplul commented Jul 1, 2024

Out of curiosity, do you know any driver that does the ff-memless way of the duration update and does not depend on ff-memless?

Logitech wheels are the only ones that I'm sure of.

I'm in favor of keeping the current (Windows) behavior that maybe incorrect, but it does not break SDL, Wine, and probably more programs that expect the Windows behavior.

I'm fine with that.

@berarma Sorry to ping you this late into a thread but TL;DR: It seems like most projects assume DirectInput semantics w.r.t. effect duration updating, meaning that an effect update doesn't reset the length counter. Any thought on this?

@berarma
Copy link

berarma commented Jul 1, 2024

@berarma Sorry to ping you this late into a thread but TL;DR: It seems like most projects assume DirectInput semantics w.r.t. effect duration updating, meaning that an effect update doesn't reset the length counter. Any thought on this?

Don't worry. This is an interesting subject that we should talk about now that there's an increasing number of people collaborating on wheel drivers.

I've already had a similar talk where someone patching a driver wants to fix any Wine/Proton/SDL/game bug in the driver, because since they're already modifying the driver, it's easier than having to create a fix for Wine/Proton/SDL.

The problem with this is that we're fragmenting the Linux FFB API and we won't ever have a stable API if we work this way. And the drivers will become increasingly complex, having to deal with incompatible one-game fixes which might break other games.

Trying to follow the Windows API in the Linux drivers has the same problems, I think there has to be a clear separation, and Wine/Proton is the compatibility layer. Any issue with Windows API to Linux API translations should be addressed in those projects.

Since the Linux FFB API documentation is scarce, we must rely on the source code and previous drivers implementations. The most important one being ff-memless because it's probably the most used in practice, and I think the only one in use for wheels until recently. Native games and even Wine/Proton have been tested against it.

In addition, I think the semantics of the Linux API are clear. There's no update operation for effects, only upload and play. When we upload an effect while it's playing, we're not only updating some parameters, we're uploading a new effect that will replace the one that is playing without having to stop and play again. In this regard, I think I didn't follow correctly these semantics in my new-lg4ff driver because they weren't clear to me at the time, but the effect length is updated.

If we don't think about this carefully, we might end up with incompatible and over-complicated drivers. Applications will need to know which driver is in use to send the correct commands, breaking the separation of concerns of what a driver should do (handle the hardware) and what a user space application can expect (devices following the same conventions independently of the driver in use).

@MmAaXx500
Copy link
Contributor Author

I have searched the lkml in hope of some information.

The earliest mention of ff-memless is in the "Input patches for 2.6.18" on 2006-10-02 (lkml) though the commit is dated 2006-07-19. It was developed by Anssi Hannula. I couldn't find any previous discussions about it. Maybe it was input-ff/ff-effects previously? If yes, then it is part of "input: force feedback updates, third time" (lkml) patch set from 2006-05-30, but there are a lot of differences between them.

hid-pidff is part of the "input: force feedback updates, third time" (lkml) patch set made on 2006-05-30 by Anssi Hannula. This was developed very close to ff-memless or in the same patch set. The notable difference is that hid-pidff does not allow updates with the same duration, effectively the duration cannot be doubled. This suggests the Windows/DirectInput mindset. (details in my previous comment)

An interesting find is the proposed ff-memless-next from Michal Malý in many forms between 2013-12 and 2014-05. The initial variants were not merged because of code duplication concerns (RFC, RFC v2, RFC v3, PATCH, PATCH v2) the second variants that are targeted to replace ff-memless not merged either, but I don't know the exact reason (PATCH, PATCH v2, PATCH v3, PATCH v4).
The summary of the patch set states:

All effects are handled in accordance with Microsoft's DirectInput/XInput.

however, the effect update is still using the ff-memless method of extending/restarting the whole effect.
In PATCH v2 from the initial variants (lkml) the effect updating/restarting was discussed, but the only guideline that Anssi can point to was the already known ff wiki article.

There was a discussion on 2014-02-15 with the subject of "Questions about the documentation/specification of Linux ForceFeedback input.h" (lkml) Unfortunately the duration update was not brought up, only the infinite duration. Through the whole discussion, the DirectInput is used as a reference point.

@berarma
Copy link

berarma commented Jul 2, 2024

I have searched the lkml in hope of some information.

The earliest mention of ff-memless is in the "Input patches for 2.6.18" on 2006-10-02 (lkml) though the commit is dated 2006-07-19. It was developed by Anssi Hannula. I couldn't find any previous discussions about it. Maybe it was input-ff/ff-effects previously? If yes, then it is part of "input: force feedback updates, third time" (lkml) patch set from 2006-05-30, but there are a lot of differences between them.

hid-pidff is part of the "input: force feedback updates, third time" (lkml) patch set made on 2006-05-30 by Anssi Hannula. This was developed very close to ff-memless or in the same patch set. The notable difference is that hid-pidff does not allow updates with the same duration, effectively the duration cannot be doubled. This suggests the Windows/DirectInput mindset. (details in my previous comment)

An interesting find is the proposed ff-memless-next from Michal Malý in many forms between 2013-12 and 2014-05. The initial variants were not merged because of code duplication concerns (RFC, RFC v2, RFC v3, PATCH, PATCH v2) the second variants that are targeted to replace ff-memless not merged either, but I don't know the exact reason (PATCH, PATCH v2, PATCH v3, PATCH v4). The summary of the patch set states:

All effects are handled in accordance with Microsoft's DirectInput/XInput.

however, the effect update is still using the ff-memless method of extending/restarting the whole effect. In PATCH v2 from the initial variants (lkml) the effect updating/restarting was discussed, but the only guideline that Anssi can point to was the already known ff wiki article.

There was a discussion on 2014-02-15 with the subject of "Questions about the documentation/specification of Linux ForceFeedback input.h" (lkml) Unfortunately the duration update was not brought up, only the infinite duration. Through the whole discussion, the DirectInput is used as a reference point.

I'd take a pragmatic approach of breaking the least software, or even not breaking any if possible. I wouldn't mind much who did what first if it wasn't ever significantly used. ff-memless is used by many FFB devices and changing its API would break them.

In this case, since the drivers which aren't following standard practice where hardly used (they weren't fully working even), like hid-pidff, I think we can easily avoid breaking what was already working. With new drivers like hid-tmff2 is even easier to avoid breakage.

ff-memless-next is out of the question since it wasn't merged and I don't think it will for the same reasons I pointed above. I feel like we're discussing if we should all go our own separate ways changing the API as we please. I hope this is finally not the case because it would certainly make our lives harder, and merging into the kernel would be a no-no.

@Kimplul
Copy link
Owner

Kimplul commented Jul 2, 2024

From the LKML thread at https://lore.kernel.org/all/[email protected]/

As you've seen, the effect model should very closely match the DInput
model (as that is what the devices tend to support), mostly just the
units are different (our units use the 16-bit range as a whole, i.e.
there are no out-of-range values).

Vague language of course, but it kind of seems like the Linux FFB is intended to match DInput, and a programming bug has just spread to become the most common behaviour (due to how many devices use ff-memless). Possibly a case of an implementation becoming the standard, but definitely lacking documentation.

I'm not opposed to making this driver behave as ff-memless, as long as we document it as the expected behaviour under Linux, and preferably send out patches to make sure the biggest users of FFB (SDL, Wine) are aware of this difference between Windows and Linux. As previously stated, I'm not aware of any programs or projects that rely on one behaviour or the other (please feel free to show me counter-examples!) so this is probably not a particularly critical detail to get right, but it'd be nice if everyone was on the same page at least.

@berarma
Copy link

berarma commented Jul 2, 2024

From the LKML thread at https://lore.kernel.org/all/[email protected]/\n\nAs you've seen, the effect model should very closely match the DInput\nmodel (as that is what the devices tend to support), mostly just the\nunits are different (our units use the 16-bit range as a whole, i.e.\nthere are no out-of-range values).

He was talking about the effect model, which is pretty close already, while this discussion was about semantics, or so I understand. I just want everyone to understand the important implications of going incompatible with other drivers.

If this change isn't important, then we're just discussing this because we should be compatible with the Windows API. What's the reason when there's already a compatibility layer that takes care of that? MS might change or deprecate this interface for a new one any day. Should we be following their mood? I don't think making our drivers dependant on what MS does with Windows is good. While it might have been a good starting point and an inspiration, following what they do shouldn't be our end goal.

@Kimplul
Copy link
Owner

Kimplul commented Jul 2, 2024

He was talking about the effect model, which is pretty close already, while this discussion was about semantics, or so I understand.

I thought a model defines the semantics, but I guess we must have different definitions?

If this change isn't important, then we're just discussing this because we should be compatible with the Windows API.

Sorry, must've expressed myself poorly. I don't mean that we should automatically follow what Windows does, the confusion here (on my part at least) lies in the fact that Wine and SDL, which I imagine together account for the vast majority of all FFB use, seem to assume that the duration parameter works the same on Linux and Windows. At least to me it's not obvious if the issue is drivers implemeting some (poorly defined) API incorrectly or users using it wrong but I'm fine with just picking one and documenting the choice clearly so other people don't have to worry avout this particular problem ever again, and right now I'm leaning towards extending durations with updates. Does that seem reasonable?

@berarma
Copy link

berarma commented Jul 2, 2024

For me, the effect model is the structs, the data. I think this is common terminology.

Extending durations seems reasonable because we aren't breaking compatibility with existing drivers. I hope I'm making it clear it's not just my preference.

If Wine isn't translating correctly or SDL isn't working properly, it will already be an issue for existing drivers that will have to be fixed at some point.

If there never was a case for this use, maybe they are unaware of the issue. But it's no less important to keep consistency between drivers.

@MmAaXx500
Copy link
Contributor Author

Just to make sure everyone is on the same page. I may have described the situation inaccurately earlier, sorry for that.
What we are speaking about is not just one parameter of the effect, it's all the parameters that changes with time like delay, attack length/force, fade length/force, ramp start/end level, phase, saturation and coefficient.

If we are going the ff-memless way, then I think it would be good to make sure that at least the larger projects (like Wine/Proton and SDL) and other wheel drivers are going to make changes / accept patches to adapt these changes. There is no point in being correct if no one wants to do it that way. But even before everything, a small scale test can be done to see if we can achieve satisfactory results.

@berarma
Copy link

berarma commented Jul 3, 2024

There is no point in being correct if no one wants to do it that way. But even before everything, a small scale test can be done to see if we can achieve satisfactory results.

What do you mean? All ff-memless devices are doing it this way, this is different to "no one wants to do it that way". Projects like Wine, Proton or SDL will do whatever works with the kernel drivers, and right now the only drivers in the kernel which are actually in use are ff-memless. If there are others it would be interesting to know.

If this case isn't currently addressed correctly, the most logic reason could be that it rarely happens, making it even less convenient to make breaking changes in the kernel. As previously said, the most common case is setting the length infinite in the initial and later effect uploads.

You could start a discussion involving Wine, Proton, SDL and kernel developers to change how ff-memless works, but I guess it'll be much more difficult than fixing Wine/Proton/SDL to work correctly with the current state. Wheel/FFB discussions usually get stalled because there's little interest, specially when they involve changing kernel drivers.

Also, to start such a discussion there should be a rationale to break backwards compatibility for many FFB devices. Maybe I've missed some message but I think I haven't read the reasons yet.

@Kimplul
Copy link
Owner

Kimplul commented Jul 3, 2024

What we are speaking about is not just one parameter of the effect, it's all the parameters that changes with time like delay, attack length/force, fade length/force, ramp start/end level, phase, saturation and coefficient.

Probably good to bring those up as well, sure.

Seems like ff-memless & co. pretty much just treat an upload as a restart, including waiting delays and running the envelopes again. From what I can tell from the source code, delays and envelope effects are calculated based on the play_at member, which is set to the upload time + effect delay on an upload: https://elixir.bootlin.com/linux/latest/source/drivers/input/ff-memless.c#L472

I don't think I have any ff-memless devices so I can't really confirm, unfortunately. The T300 definitely doesn't do this on an upload so in our case that would mean that each effect update would have to finish with sending a PLAY packet to fully restart the effect. This also effectively means that emulating DInput is really quite difficult, for example translating a DInput magnitude change on an effect with a delay would mean having to calculate how much time is left in the delay and subtract that from the existing Linux effect. Same and probably more annoying with the envelopes.

The FFB docs say 'Normally, the effect is not stopped and restarted' which I guess should be read as 'Normally, the effect is not stupped, and is restarted' assuming ff-memless has the correct interpretation of it, though I don't entirely understand how a stop + start is different from a restart.

Alternatively, the quote could've been paraphrased from DInput, which states

Normally, if the driver cannot update the parameters of a playing effect, the driver is permitted to stop the effect, update the parameters, and then restart the effect.

This makes more sense to me, restarting the effect every time it is updated is a bit silly if the wheel itself supports partial updates.

@Kimplul
Copy link
Owner

Kimplul commented Jul 3, 2024

right now the only drivers in the kernel which are actually in use are ff-memless. If there are others it would be interesting to know.

There's at least the Logitech HID++, which seems to just dump the effect buffer as a whole to the wheel during an update:
https://elixir.bootlin.com/linux/latest/source/drivers/hid/hid-logitech-hidpp.c#L2543

I don't know how the wheel's firmware itself reacts to the delays, but could be cool to find someone with a G923 and ask.

@MmAaXx500
Copy link
Contributor Author

MmAaXx500 commented Jul 3, 2024

What do you mean? All ff-memless devices are doing it this way, this is different to "no one wants to do it that way". Projects like Wine, Proton or SDL will do whatever works with the kernel drivers, ...

I mean that even if one of Wine, Proton, or SDL is opposed to adapt the ff-memless way of doing updates (I have no information about any of them) it's worse than having the current state. Currently, as far as my research gone, the userspace is mostly (*) uniformly ignoring the ff-memless behavior and expects DirectInput-style updates.

*: The only project I found that clearly aware of ff-memless is "qt-mobility-haptics-ffmemless" code. Maybe there are others that are closed source or not on github.

... right now the only drivers in the kernel which are actually in use are ff-memless. If there are others it would be interesting to know.

There is hid-pidff that is in the kernel that is the USB PID standard implementation. I know that OpenFFBoard is using it, but I can imagine there are more devices because of its generic nature. (I know that I brought this up a few times already, and I know that you reacted to it)


This also effectively means that emulating DInput is really quite difficult, for example translating a DInput magnitude change on an effect with a delay would mean having to calculate how much time is left in the delay and subtract that from the existing Linux effect. Same and probably more annoying with the envelopes.

In case of this driver (hid-tmff2), it would mean that a program (something 3rd party, game, whatever) has to keep track of time and recalculate every effect parameter based on the elapsed time on every update then send it to this driver where it is converted back to DInput-like values (also keeping track of time) to be consumable by the wheel, effectively undoing what every program has to do to be compatible with ff-memless.

@MmAaXx500
Copy link
Contributor Author

There's at least the Logitech HID++, which seems to just dump the effect buffer as a whole to the wheel during an update: https://elixir.bootlin.com/linux/latest/source/drivers/hid/hid-logitech-hidpp.c#L2543

I don't know how the wheel's firmware itself reacts to the delays, but could be cool to find someone with a G923 and ask.

I don't own the wheel but found the documentation directly from Logitech: https://opensource.logitech.com/wiki/force_feedback/Logitech_Force_Feedback_Protocol_V1.6.pdf

In the 4.1.3 x8123.2 downloadEffect section says

The downloadEffect method is used to create and update force effects on the device. The first byte decides if the effect is to be allocated fresh (EFFECT_ID == 0), or if an existing effect should be modified (0 < EFFECT_ID <= SLOT_COUNT). The first byte of the response contains either 0x00 if there was an error, or the EFFECT_ID.

In hid-logitech-hidpp the START and AXIS seems to be always set to 0 and EFFECT_ID is 0 if new effect is uploaded otherwise not, so updates are used, but the doc is not clear about how an update is handled.
Yes, if someone can try this out on real hardware, that would be the best.

@berarma
Copy link

berarma commented Jul 5, 2024

So the reason is that it's easier to just let the firmware do whatever it does. Thus you'd really want the API to be open about it and not force any particular behavior. It just happens that this wheel behaves more closely to DInput for this case.

If I didn't convince you then go ahead and take the easy path. I don't think an excuse is needed. It won't have consequences in the short term as far as we know. Given the state of neglect of this area of the kernel, I guess it will become a mess anyway,

@MmAaXx500
Copy link
Contributor Author

So the reason is that it's easier to just let the firmware do whatever it does. Thus you'd really want the API to be open about it and not force any particular behavior. It just happens that this wheel behaves more closely to DInput for this case.

I didn't mean that, I only said how it is going to look like in hid-tmff2 (in case if you are replying to the paragraph that starts with "In case of this driver ...").
However, I think hardware implementations should be considered when an API is made or changed. We don't control the manufacturers that are creating these devices and what they are going to do is what is easier to them undoubtedly the Windows market share is larger than Linux (considering gaming) and the manufacturers are designing their hardware to work with the Windows API.

So the reason is that it's easier to just let the firmware do whatever it does.

Well, that's how the first FFB driver born for I-Force wheels way before ff-memless, this is the first driver that introduced the term "update". I-Force has separate slots for the "core" effect and the "modifiers", changing the "core" requires a restart but the "modifiers" can be changed on the fly. I can't be sure about the exact behavior, but why would there be slots if they are restarting the whole effect? I think it works DInput-like. Also, it was not changed to ff-memless like other devices when ff-memless was introduced, and it's almost untouched since then over here: https://elixir.bootlin.com/linux/v6.9.8/source/drivers/input/joystick/iforce
When I-Force got its update capability: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git/commit/drivers/input?id=07b8fb25a96707cdd01072fd6298c2b8570801be

After the I-Force got its update capability, the "Dynamic update of an effect" section changed from

his consists in changing some parameters of an effect while it's playing. The
driver currently does not support that. You still have the brute-force method,
which consists in erasing the effect and uploading the updated version. It
actually works pretty well. You don't need to stop-and-start the effect.

to

Proceed as if you wanted to upload a new effect, except that instead of
setting the id field to -1, you set it to the wanted effect id.
Normally, the effect is not stopped and restarted. However, depending on the
type of device, not all paramaters can be dynamically updated. For example,
the direction of an effect cannot be updated with iforce devices. In this
case, the driver stops the effect, up-load it, and restart it.

Src: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git/commit/Documentation/input/ff.txt?id=3dfd8b7d5c3fa66bccbbb9fa4d917df7f5dca751


Some observations of mine:

The difference between ff-memless and others are only visible when an effect is played with an envelope, delay or when the duration of the effect is important. Otherwise, the difference is invisible, I think.

Most of the devices that are depends on ff-memless are gamepads with some rumble motors, and low-level chips that are lacking the concept of an "update". Gamepads are incapable of playing force feedback effect with precision because they are not designed for that, hence harder to notice something that is already rare.


You asked previously:

right now the only drivers in the kernel which are actually in use are ff-memless. If there are others it would be interesting to know.

It depends on how you define "actually in use" but here is what I found. I only include wheels that are probably capable of more than rumble.

Depends on ff-memless

  • hid-lg4ff, in tree

    • Logitech wheels
  • hid-emsff, in tree

    • EMS Trio Linker Plus II

Works like ff-memless

  • new-lg4ff, out of tree
    • Logitech wheels

Likely DInput-like

  • hid-logutech-hidpp, in tree
    • Newer Logitech wheels

DInput-like

  • iforce, in tree

    • Immersion I-Force
  • hid-fftec, out of tree

    • Fanatec
  • hid-tmff2, out of tree

    • Thrustmaster (excluding T150)
  • hid-pidff, in tree

    • Simucube 1, 2
    • SimXperience 3
    • OpenFFBoard
    • Simagic?
    • Moza?
  • hid-simagic-ff, out of tree, hid-pidff copy

    • Sigmagic
  • hid-moza-ff, out of tree, hid-pidff copy

    • Moza

Based on these, I think we can't speak about some defined behavior on the kernel side either, so if we want something that's working the same for every device, then a change in the kernel is unavoidable.

At this point, I see 3 possible decision for this discussion. We are not under pressure to do something, so I think all the 3 are equally valid.

Leaving everything as-is

  • Required changes: None
  • Exactly defined kernel and userspace behavior: No

Adapt to ff-memless

  • Required changes: Probably most of the already existing applications that are using this API directly. What I found is mostly translation layers like SDL, Wine and OIS.
  • Required changes: In and out of tree kernel drivers
  • Exactly defined kernel and userspace behavior: Yes, ff-memless

Adapt to DInput

  • Required changes: I'm not aware of too many applications, only "qt-mobility-haptics-ffmemless". There are more probably
  • Required changes: In and out of tree kernel drivers
  • Exactly defined kernel and userspace behavior: Yes, DInput (like)

In both cases, when something is changed, closed-source applications should be considered for their current behavior because they cannot be changed easily. We can think that they are going to adapt, but when?

Judging from a previous attempt to change one line of hid-pidff, we are probably not going to go too far with these kernel changes. I think the "Leaving everything as-is" is the only achievable option, but if any of you know how to make any of the other options happen, I open to them probably both of you know more about kernel stuff than me, I'm only digging in the kernel for the last 7 days.

@Kimplul
Copy link
Owner

Kimplul commented Jul 13, 2024

Right, I think we've let this stew for long enough. I at least haven't come up with anything and agree that probably the best course of action for now is to let things be the way they are. My opinion's been kind of flip-flopping over the course of this discussion, but I believe we uncovered some interesting aspects that I've never considered before and I'm happy it was brought up. It can always be resurrected if someone finds a real usecase that is affected.

@MmAaXx500 Thanks for a sober and really well put together overview of the topic, again great work. Do you think you have everything you need to get this PR over the finish line?

@MmAaXx500
Copy link
Contributor Author

Do you think you have everything you need to get this PR over the finish line?

Pushed the last commits to use the 4c update packet and included the new offset field in the update. I haven't made any behavioral changes regarding updates.
I don't plan to make any more changes in this PR.

@Kimplul
Copy link
Owner

Kimplul commented Jul 13, 2024

Pushed the last commits to use the 4c update packet and included the new offset field in the update. I haven't made any behavioral changes regarding updates.

Alright, good to know. You mentioned previously

I saw some packets that merged duration update with the other parameters, I will check it tomorrow.

which I thought meant that there were still some changes coming.

I previously ran some games and everything seemed to work fine, so added some final stylistic nitpicks, if you could sort them out I'd be happy. They're all really very minor and I could go in and fix them later myself, but that wouldn't be as hygienic so I'd prefer it if they were attended to before I merge.

@MmAaXx500
Copy link
Contributor Author

which I thought meant that there were still some changes coming.

There was a commit below that comment, but I force pushed since then, and it is not visible there anymore (it's probably a bad practice on my side, sorry about that). It was the 6e0d76d and replaced by f595ade in the today's push.

You can see there what I changed today: https://github.com/Kimplul/hid-tmff2/compare/6e0d76d71d21864a0b138b9c29e81a8c2a779e6d..f595adeeca291e37260ea731fbfaa90be376deb8

added some final stylistic nitpicks, if you could sort them out I'd be happy.

I don't see any. Did you submit them?

if(sat == 0)
return max;

return sat * max / 0xFFFF;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra space here as well, and the hex constant should be with small letters to fit into the existing code a bit better.

{
/* max deadband value is 0x7FFF in either direction */
/* deadband is the width of the deadzone, one direction is half of it */
*out_rband = clamp(offset + (deadband / 2), -0x7FFF, 0x7FFF);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hex letters and I probably should've mentioned earlier but line length should be ~80 characters, maybe split the signature between out_lband and deadband.


memcpy(&packet_damper->damper_start, damper_values, ARRAY_SIZE(damper_values));
t300rs_fill_timing(&packet_damper->timing, duration, offset);
t300rs_fill_timing(&packet_condition->timing, duration, offset);

ret = t300rs_send_int(t300rs);
if (ret)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"failed uploading spring" should probably be changed to "failed uploading condition"
(GitHub won't let me comment on unchanged code apparently, couple lines below here)

{
if(effect_type == FF_SPRING)
return 0x6aa6;

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are extra spaces on this line. Empty lines should generally be truly empty, not have invisible characters. Some editors leave extra spaces around for whatever reason.

input_level = 100;
break;
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.


}
t300rs_calculate_deadband(&right_deadband, &left_deadband, cond.deadband, cond.center);
t300rs_calculate_deadband(&right_deadband_old, &left_deadband_old, cond_old.deadband, cond_old.center);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines are a bit on the long side, suggest splitting arguments 2+2.


t300rs_fill_header(&packet_damper->header, effect.id, 0x64);
memcpy(&packet_condition->hardcoded, condition_values, ARRAY_SIZE(condition_values));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a bit too long here as well, suggest putting ARRAY_SIZE(...) on new line.

uint8_t damper_start[17];
struct t300rs_packet_timing timing;
} *packet_damper = (struct t300rs_packet_damper *)t300rs->send_buffer;
t300rs_calculate_deadband(&right_deadband, &left_deadband, cond.deadband, cond.center);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest splitting arguments 2+2.

@Kimplul
Copy link
Owner

Kimplul commented Jul 13, 2024

I don't see any. Did you submit them?

Apparently not, thanks for pointing it out. I thought I was just leaving comments but I must've accidentally started a review at some point, should be visible now.

@MmAaXx500
Copy link
Contributor Author

Addressed all the requested changes.

@Kimplul Kimplul merged commit bb4817e into Kimplul:master Jul 13, 2024
@Kimplul
Copy link
Owner

Kimplul commented Jul 13, 2024

Great, thank you!

@MmAaXx500 MmAaXx500 mentioned this pull request Jul 19, 2024
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants