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

Add Force Feedback support to vjoy and joystick plugin #48

Open
AndersMalmgren opened this issue Jan 28, 2015 · 154 comments
Open

Add Force Feedback support to vjoy and joystick plugin #48

AndersMalmgren opened this issue Jan 28, 2015 · 154 comments

Comments

@AndersMalmgren
Copy link
Owner

I have not checked if the C# SDK has the support yet, but here is a demo written in C
http://sourceforge.net/p/vjoystick/code/HEAD/tree/branches/Incompatible/ForceFB/apps/FfbMon/FfbMon.cpp

http://vjoystick.sourceforge.net/site/index.php/forum/5-Discussion/393-force-feedback-support?start=20

@aubade
Copy link

aubade commented Mar 3, 2016

Is there any chance of this hitting mainline soon? I'd really love to be able to get FFB effects in games that need vjoy for input device merging.

@MarijnS95
Copy link
Contributor

What a coincidence you just asked that @aubade :)

I've been working on it in the past but there are some nasty problems. But finally I've gotten around to a seemingly working version.
https://github.com/MarijnS95/FreePIE_FFB
Be aware though, the code:

  • Does not follow correct coding practices. (see this as a testbench where I mess around)
  • Hasn't been tested (well you're gonna laugh: I don't have an FFB device, I'm just extremely interested in this stuff and building it for some other people)
  • Still has some problems with disposing devices and effects. Like I said the code is a bit messy ;)

I might need some help from Anders to get the code working with proper practices, and I should definitely get an FFB wheel to continue development.

@aubade
Copy link

aubade commented Mar 6, 2016

Fantastic! I just got the thing to compile (Though, just to warn, I had to delete some $ characters in the code; google indicated they were probably because of failed copy-pastes?), but I'm not sure how to set up a script to do this? If you can give me some example scriptcode I'd be happy to help you test this. (I've got a Logitech Wingman Strike Force 3D atm. Also a very old iForce joystick that won't work past WinXP, but I could still try playing around with that if it's of interest and freepie can still run on XP)

@MarijnS95
Copy link
Contributor

The dollar signs are a new C# 6.0 String Interpolation feature. Basically string.Format("hello {0}", Name) is equivalent to $"hello {Name}".

This will create a slowly ramping up force (only SlimDX test):

if starting:
    jID = 1;#ID of your FFB device
    joystick[jID].CreateEffect(1, EffectType.ConstantForce, -1, [1, 0])
    joystick[jID].OperateEffect(1, EffectOperation.Start, 0)
    force = 0

diagnostics.watch(force)
if(force < 10000):
    force += 1
if(force % 100 == 0):
    joystick[jID].SetConstantForce(1, force)

And this will forward FB data (for the moment being only constant force):

if starting:
    vJoy[0].RegisterFFBDevice(joystick[2]) #vJoy[0] is the receiving vJoy device that is connected to the game, joystick[2] is the FFB device

@AndersMalmgren
Copy link
Owner Author

Nice work on getting something to work! We are still compatible with VS2010 (I think haven't tried in a while :D). Using String Interpolation from VS 2015 will break that, its only a compile time feature and not CLR so you can still run the program on CLR with .NET 4.0 but you will need VS2015 to compile it, haven't decided if we should require our devs to use VS2015 or not just yet.

I looked at the code and its a huge mess right now :D Could you clean in up and I can assist you in getting it more FreePIE like? I do not have a force feedback device either

@AndersMalmgren
Copy link
Owner Author

Btw I also see there are alot of changes that are not really changes, do you use the correct tab setting (Ínsert spacec 4)

@MarijnS95
Copy link
Contributor

I can change the string interpolations to string.Format for the time being (I'm too used to them, totally forgot about older version compatibility). Haven't looked at similar formatting either, so my code still has tab indents. I'll fix that.

Yes it's huge mess, for two main reasons: 1. I haven't been able to test it so I'm not even sure if the current approach works (hence I first want to know that before progressing).
And 2: I first want to discuss with you how to proceed. There's multiple different approaches/implementations I can think of, but I've no idea which would suit FreePIE best. However I don't think this (issue tracker) is the best place to do that, I think moving to the forum or direct (voice) chat is the best option? Then I can explain how FFB works in both vJoy and SlimDX, and we can come up with a plan how to implement this in such a neat way as the rest of FreePIE.

However, what I think for now (very basic plan) is to have an easy oneline "automatically forward FFB commands from vJoy device to joystick", and a complete Python implementation so users can modify/forward FFB data however they wish.

@aubade
Copy link

aubade commented Mar 6, 2016

Okay, so the first snippet works fine! The automatic nonff-compatability-mode autocentering shuts down on script-run and I feel a stick-forward force slowly intensify. The centering spring returns as soon as I shut the script down.

Unfortunately, the second snippet seems to not work at all; I've tried an external application ( http://www.fs-force.com/support.htm has a program called ForceTest that can send arbitrary effects to arbitrary sticks), as well as combining both scripts.

When the scripts are combined; registering the real stick as vjoy's ffb target, then sending effects to the vjoy stick, the autocentering never disables and I never feel the force. (however, if set to send effects directly to the real stick, it does still work).

I've copied the console output when running this combined script if it's of any help at all.

#code snippet
if starting:
    vJoy[0].RegisterFFBDevice(joystick[1]) #vJoy[0] is the receiving vJoy device that is connected to the game, joystick[2] is the FFB device
    jID = 0;#ID of your FFB device
    joystick[jID].CreateEffect(1, EffectType.ConstantForce, -1, [1, 0])
    joystick[jID].OperateEffect(1, EffectOperation.Start, 0)
    force = 0

diagnostics.watch(force)
if(force < 10000):
    force += 1
if(force % 100 == 0):
    joystick[jID].SetConstantForce(1, force)

Log output: https://dl.dropboxusercontent.com/u/187059/vjoyffb.txt

@MarijnS95
Copy link
Contributor

It's weird that it doesn't work since it receives all the correct data and sets the settings correctly. The only difference I know of is polar vs cartesian coordinates. I'll modify it and see if that makes a difference.

Log doesn't really help since it's missing the beginning (I should've indented the if(force % 100 == 0): and SetConstantforce below so that it doesn't keep setting that value. However I can see the same log here as well (even tho I don't own an FFB device doesn't mean I can't log what the vJoy device receives, or even forward from vJoy device 0 to 1, however I'm not sure if that causes any problems since all the vJoy stuff is static - the callback triggers for each device rather than per device, have to split that up myself)

Btw can you try one more thing? Move the OperateEffect line to directly below SetConstantForce, something like this:

if starting:
    vJoy[0].RegisterFFBDevice(joystick[1]) #vJoy[0] is the receiving vJoy device that is connected to the game, joystick[2] is the FFB device
    jID = 0;#ID of your FFB device
    joystick[jID].CreateEffect(1, EffectType.ConstantForce, -1, [1, 0])
    force = 0

diagnostics.watch(force)
if(force < 10000):
    force += 1
if(force % 100 == 0):
    joystick[jID].SetConstantForce(1, force)
    joystick[jID].OperateEffect(1, EffectOperation.Start, 0)

Might be that the effect needs to be restarted every time it's changed (at least that's what I've seen when reading force data from another game... It kept sending start operations (even without modifying the magnitude each time))

@aubade
Copy link

aubade commented Mar 6, 2016

Sadly, this doesn't seem to be working either; Same joystick-side behavior as above. This time I stopped the program much earlier so as to catch the beginning of the log:

https://dl.dropboxusercontent.com/u/187059/vjoyffb2.txt

Incidentally, I don't know if this is important but every even-numbered time (second, fourth, sixth, etc) I try to run this or my earlier attempted script, it errors out with the error "Unable to create effect: An item with the same key has already been added." on the CreateEffect line, however on odd-numbered run attempts it does run.

@aubade
Copy link

aubade commented Mar 6, 2016

Oh blight it all, i'm very sorry; the not-working thing was my fault. I'd ignored a vjoy "please reboot" prompt the other day; after rebooting, my snippet indeed works... but with a stick-back force instead of a stick-forward force. It, however, crashes FreePIE upon script stop. Your revised script makes the force cut in and out

@MarijnS95
Copy link
Contributor

Wow, you just posted that when I wanted to respond :D So it works with a game or that ForceTest application now?

I know of weird behaviour with effects, dispossing etc, I haven't yet been able to look at it... Multiple packets are sent to dispose every effect separately, and to dispose them altogether. FreePIE disposes the joystick/vJoy devices as well, probably ending up in some problems.

@aubade
Copy link

aubade commented Mar 6, 2016

Unfortunately, external applications don't seem to be working atm. The games I've tried (Freespace 2's SCP and IL-2 Sturmovik 1946) don't give any errors, but I don't feel any forces. ForceTest gives an error on device select/reinitialize, at least:

Enumerated Joystick : vJoy Device
Enumerated Joystick : Logitech WingMan Strike Force 3D USB
Supported Effects : 
   - Constant
   - Ramp Force
   - Square Wave
   - Sine Wave
   - Triangle Wave
   - Sawtooth Up Wave
   - Sawtooth Down Wave
   - Damper
   - Inertia
   - Friction
   - CustomForce

Creating effects for vJoy Device
Error creating Spring Effect. Your joystick may not support this effect.  : 
REGDB_E_CLASSNOTREG : 
Class not registered

And gives an error when applying the grooves effect:

Error starting Pavement Groove effect. : 
E_HANDLE : 
Invalid handle

but apart from that, effects are silently discarded.

@MarijnS95
Copy link
Contributor

The only effect that is currently completely forwarded is Constant(Force).

@aubade
Copy link

aubade commented Mar 6, 2016

Okay, then yeah. :( Unfortunately it looks ilke Constant Force from FreePIE is fine, but from ForceTest isn't going through.

@MarijnS95
Copy link
Contributor

@aubade
Copy link

aubade commented Mar 6, 2016

Okay, from FFB inspector it works, if inconsistently; the program seems a bit crashy. I have to unplug the real joystick, start FFB inspector, replug real joystick, start FreePIE, run script, otherwise FFBinspector crashes. Then, sometimes it doesn't actually generate the force when i tell it to start effect, but when it does work, I do feel the forces.

@MarijnS95
Copy link
Contributor

Cool, finally it 'works' 👍

I guess that's a problem when scripts stop, things are not correctly disposed, event handler thingies (vJoy exports a method to register a callback method, but no way to get rid of it, so it seems it's held until FreePIE has been restarted). I'll have to look at that later, maybe after I explain to Anders how it works he has some ideas. But at least the basic part is working, which I'm pretty happy about.

@aubade
Copy link

aubade commented Mar 6, 2016

Fantastic! feel free to @ me anytime if you need my help.

@MarijnS95
Copy link
Contributor

I will, though I don't expect to work much on this next week. That is, unless I make some big steps with Anders, as I'm not planning to work a lot only to find out there's a much smarter design/implementation possible. Besides, university (and The Division which is released tuesday) is taking up my time.

@AndersMalmgren
Copy link
Owner Author

I created a thread at MTBS that we can use to talk development and clean up of the code.

http://www.mtbs3d.com/phpBB/viewtopic.php?f=139&t=21859

@AndersMalmgren
Copy link
Owner Author

I have added some more logging which have found more problems with the forwarding code. Please check the forum, you probably have more knowledge into the Ffb protocol

@MarijnS95
Copy link
Contributor

I've read the forum, just didn't have any time to test anything last week.

@jbinard
Copy link

jbinard commented Nov 15, 2016

Any news about the force feedback support ?

@AndersMalmgren
Copy link
Owner Author

Hi, im not active on the FreePIE project right now, are working on a VR game full time. But it seems there has been some progress, my suggestion is contacting MarijnS95 in this thread

http://www.mtbs3d.com/phpBB/viewtopic.php?f=139&t=21859&start=40

@MarijnS95
Copy link
Contributor

@jbinard I'm not that active on it anymore. I was about a month ago (did some huge refactors/cleanups as well as some major breakthoughs packet-wise), but the fact I still don't have an FFB device means it's almost impossible for me to complete it; I have to rely on other people to do the testing/debugging, without proper tools or knowledge about the inner workings. Quick iteration is impossible as I have to send a compiled build out every time I make a tiny change; then request others to test exactly what I want and pray I logged the correct information, which takes days instead of minutes when done locally.

That said though, the forum is the best way to discuss. The version on github/the forum is a bit dated already, mostly missing some minor fixes and debugging stuff everywhere.

@erik-smit
Copy link

erik-smit commented Feb 12, 2017

I've been toying with this for a bit. Here's some notes for any others trying this.

What I've mostly ran into is this line returning an E_INVALIDARG: https://github.com/MarijnS95/FreePIE/blob/Ffb/FreePIE.Core.Plugins/Dx/Device.cs#L194

I've noticed this only happens when using Polar coords. If I change the -1 in 'createEffect' to '0'.
I get a wheel turning back and forth with the script below.

if starting:
    jID = 0;#ID of your FFB device
    joystick[jID].createEffect(0, EffectType.ConstantForce, -1, [0, 1])
    force = 0

diagnostics.watch(force)
if(force < 10000):
    force += 10
else:
    force = -10000

if(force % 100 == 0):
    joystick[jID].setConstantForce(0, force)
    joystick[jID].operateEffect(0, EffectOperation.Start, 0)

Also, sometimes Unable to create new effect: An item with the same key has already been added..

@MarijnS95
Copy link
Contributor

MarijnS95 commented Feb 12, 2017

What I've mostly ran into is this line returning an E_INVALIDARG: https://github.com/MarijnS95/FreePIE/blob/Ffb/FreePIE.Core.Plugins/Dx/Device.cs#L194
I've noticed this only happens when using Polar coords. If I change the -1 in createEffect to '0'.
I get a wheel turning back and forth with the script below.

Which is as expected; I 'recently' changed the function signature. In the past the -1 represented the duration (-1 meaning 'infinite'), but now it's a boolean for switching between polar and Cartesian coordinates (-1 evaluates to True, meaning "use polar coordinates"). In that case, the first argument of the array should represent the angle, and the second one must be 0 (which is not the case with the Cartesian parameters [0, 1]).
See https://msdn.microsoft.com/en-us/library/windows/desktop/ee417536(v=vs.85).aspx:

Setting up the direction for a polar two-axis effect is only a little more complicated. Set the DIEFF_POLAR flag in dwFlags and set rglDirection to point to an array of two LONGs. The first element in this array is the direction from which you want the effect to come. The second element in the array must be 0.

Anyway, when using Cartesian coordinates, [0, 1] should give you an effect in the positive y-axis, which I do not expect to turn the wheel (as that is x-axis).

Also, sometimes Unable to create new effect: An item with the same key has already been added..

Yes, if you've followed my ramblings on the Spintires forum, it turns out the wheel and/or driver keeps track of these effects even when being disposed. I sent out some fixes for that on the Spintires forum, but never actually committed it until now; see here for the code that tries to avoid creating an effect when it exists. According to Lombra on the ST forum, it doesn't seem to work.

However, when I put "vJoy[0].registerFfbDevice(joystick[dev])" in my script, I seem to be getting some forcefeedback from Spintires (game) passed to my wheel, so yay!

Not sure why this was removed by an edit. False positive?

@erik-smit
Copy link

Not sure why this was removed by an edit. False positive?

Correct.

@erik-smit
Copy link

Is it supposed to work/do something?

For my purpose, trying to simulate a stickshift with the sequential shifter of my Logitech DF GT for Spintires, the passthrough of registerFfbDevice would be enough.

@cyberluke
Copy link

@AndersMalmgren
Copy link
Owner Author

Now this looks cool: https://www.mtbs3d.com/phpBB/viewtopic.php?f=139&t=18724

I don't think you want todo sensitive timing things on the script thread. Its defaulted to 64hz (16ms).

You could have a thread going on in the background in the plugin for example, the Serial com plugin does that for example. Though if you don't have any I/O there will be no interupts and the thread will use all resources on that CPU core.

@cyberluke
Copy link

For now increasing the timing to 500hz did help and even vjoy latency without force feedback is much better. Thank you very much for this valuable information, I will look at it in the future!

@cyberluke
Copy link

That ComDevicePlugin did not help. I am facing another situation. Both VJoy and SlimDX only register their global handlers and there is a lot of static methods as well.

The current latency with System Timer is between 3ms and 9ms, which is great. There is occasional spike to 20ms or even 300ms.

But in SlimDX docs there is mentioned that FFB processing must be fast because it is blocking operation.

There will be some basic logic mistake somewhere in current usage of VJoy and SlimDX libraries. Even 9ms of FFB packet processing will cause stuttering to the game (even with keyboard control). When game stops sending FFB packets, there is no stuttering.

This is the last thing to solve (plus Ramp effect params tweak).

@cyberluke
Copy link

cyberluke commented Aug 27, 2022

I need to refresh my C# skills as I work in Java for the last 5 years. But something like this helps to unblock the processing, but now the DirectX device will not receive FFB information. The packets are parsed and processed and written out to console though. It will probably need some kind of synchronized block when working with Device class.

This code processes the FFB packet and response in-game is smooth. It will just not get delivered to SlimDX Device registered in Global handlers, probably due to some threading issues. No exception though.

        /// <summary>
        /// Called when vJoy has a new FFB packet.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="userData"></param>
        private static void OnFfbPacketAvailable(IntPtr data, IntPtr userData)
        {
            Task.Run<String>(async () =>
            {
                FfbPacket ffbPacket = new FfbPacket(data);
                var pa = packetMapper[ffbPacket.PacketType];
                if (pa != null)
                    queueWrapper.Add(pa.Convert(ffbPacket));

                await Task.Yield();
                return null;
            });
        }

@cyberluke
Copy link

cyberluke commented Aug 27, 2022

With Task.Run, some packets that come at the same time will get lost. Tried to add lock(Device) {} block, but that does not help.

But If I keep adjusting parameters, it gets received by some time and force feedback will work. Just need to somehow manage to not keep overwriting packets. Perhaps the data pointer IntPtr gets changed in the middle of creating the FfbPacket object.

@cyberluke
Copy link

cyberluke commented Aug 28, 2022

I have new commit here: cyberluke@65775eb

So the issue above with C++ pointers has been confirmed.

Analysis:

  1. Incoming callback use the same IntPtr all over again rewriting the data (like circular buffer)
  2. Any delay in C# callback of VJoy means Game UI is blocked - it is blocking operation!!! Instant FPS kill.

Partially Failed Solution:

  1. The only operation you want to do in C# callback is to copy IntPtr to your own memory location, so next packet will not rewrite it and then pararelize it on background (saving to blocking queue, so the order should be the same)

But it looks like this and InternalFfbPacket itself is IntPtr. So you have to deal with pointer and its nested pointer in order to do it properly!

        [StructLayout(LayoutKind.Sequential)]
        public struct InternalFfbPacket
        {
            public int DataSize;
            public CommandType Command;
            public IntPtr PtrToData;
        }

At this time I thought I'm lucky to know so many programming languages...

So in callback you want to do only this:

        public FfbPacket(IntPtr packetPtr)
        {
            //copy ffb packet to managed structure
            packet = (InternalFfbPacket)Marshal.PtrToStructure(packetPtr, typeof(InternalFfbPacket));

            _Data = new byte[packet.DataSize];
            Marshal.Copy(packet.PtrToData, _Data, 0, (Int32)packet.DataSize);

            // Convert object to pointer
            _DataPtr = GCHandle.Alloc(_Data, GCHandleType.Pinned);
            packet.PtrToData = _DataPtr.AddrOfPinnedObject();
            packetPtrCopy = Marshal.AllocHGlobal(Marshal.SizeOf(packet));
            Marshal.StructureToPtr(packet, packetPtrCopy, false);
        }

Of course you need to have Destructor as well:

        protected void Free()
        {
            Marshal.FreeHGlobal(packetPtrCopy);
            packetPtrCopy = IntPtr.Zero;
            _DataPtr.Free();
        }
  1. Now everything running smoothly, UI is not blocked and packet content is not randomly rewritten. But creating of a pinned object in memory in C# is not that fast. So now the game is smooth with non-blocking operation, but the latency have increased from 3-9ms before to 100-300ms now. Therefore it is probably time to dig into VJoy C++ source and fix it natively from there. There should be some method to clone the packet in memory and then free it.

Because this sequential solution of packets processing in C# is not fast and is blocking and cannot be paralelized easily. If you would go with ARM or ATMEL microcontroller and do the processing there, it would be much faster than in C#.

This is so painful branch to work with :-D one bottleneck next to each other. One syntax error next to each other. I take it back. The generics and async processing is useless and not working. It's 3AM again and I need to work more...

@cyberluke
Copy link

So I fixed this branch in C#, but now I need to rewrite everything in C++ because callback and rerouting of packets will be always bottleneck here :-/

@cyberluke
Copy link

Meanwhile there is pull request to fix two VJoy bugs. First is Gain Packet is always zero due to wrong type cast. Second is a mistake in helper class, which returns always 2 bytes only (but it is not used in this branch, so only Gain packet is issue).

That's why in this branch I always set min. gain to 5000: njz3/vJoy#3

@cyberluke
Copy link

cyberluke commented Aug 28, 2022

Updated FFB fork: https://github.com/cyberluke/FreePIE/tree/Ffb

So Erik removed Marjin's AsyncActionRunner because of some memory exceptions. These memory exceptions were happening because FFB callback provides Int Pointer to FFB packet, but this gets rewritten by the next packet. Marjin had some unsafe FFB branch in his fork, but the marshalling is not correct. He forgot to work with the nested Int Pointer IntPtrData inside FFB packet. Also it was slow.

So now FFBPacket constructor has been fixed and split to two parts. First starts in sync callback. Second is for async lazy load to improve latency.

        public FfbPacket(IntPtr packetPtr)
        {
            ClonePacket(packetPtr);
        }

        public void ClonePacket(IntPtr data)
        {
            unsafe
            {
                InternalFfbPacket* FfbData = (InternalFfbPacket*)data;
                int size = FfbData->DataSize;
                int command = (int)FfbData->Command;
                byte* bytes = (byte*)FfbData->PtrToData;
                inMemoryPacket = new InternalFfbPacket();
                inMemoryPacket.DataSize = size;
                inMemoryPacket.Command = FfbData->Command;
                newData = new byte[size];
                Marshal.Copy(FfbData->PtrToData, newData, 0, (Int32)size);
                FFBPType type = FFBPType.PT_STATEREP;            
                VJoyUtils.Joystick.Ffb_h_Type(data, ref type);
                PacketType = type;
            }
        }

        public void Init() {
            _DataPtr = GCHandle.Alloc(newData, GCHandleType.Pinned);
            inMemoryPacket.PtrToData = _DataPtr.AddrOfPinnedObject();
            _PacketPtr = GCHandle.Alloc(inMemoryPacket, GCHandleType.Pinned);
            packetPtrCopy = _PacketPtr.AddrOfPinnedObject();

            //Read out the first two bytes (into the base packetData class), so we can fill out the 'important' information
            uint effectId = 0;
            VJoyUtils.Joystick.Ffb_h_EffectBlockIndex(packetPtrCopy, ref effectId);
            BlockIndex = (int) effectId;

            uint deviceId = 0;
            VJoyUtils.Joystick.Ffb_h_DeviceID(packetPtrCopy, ref deviceId);
            DeviceId = (int)deviceId;
            if (DeviceId < 1 || DeviceId > 16)
                throw new Exception(string.Format("DeviceID out of range: {0} (should be inbetween 1 and 16)", DeviceId));
                
        }

In FFB callback we call FFBPacket constructor to only quickly clone data bytes into our own property without creating any C++ pointer. Plus we read Packet Type to optimize further processing decision logic.

In Init() method is the code to lazily initialize C++ pointer to our managed C# data, so underlying VJoy C++ library (C# wrapper) can be used. This happens in async method and it does not block FFB callback anymore.

This results in smooth gameplay, game UI thread does not stutter anymore. Latency delay of FFB packets is between 9-30ms with spikes of 300ms. This results in bad experience as force feedback is delayed and does not correspond to the game. This is without AsyncActionRunner.

Further addition of AsyncActionRunner only slows it down. Which is strange. The performance is worse than single thread. With incoming packets, the delay keeps increasing up to 20 seconds. It queues all packets quickly, nothing is lost. But then the actual processing of individual packets sequentially bottlenecks each other packet increasing delay. I tried to optimize it by allowing some packets to be processed in parallel and trying to discard possible duplicate packets, but that does not solve the issue.

Currently I need to implement the same solution only in C++ in order to compare the speed. This is the big issue now. It is working, but due to creating new SlimDX Effect object each packet (like 50 packets in 5ms), it kills the gaming experince.

@cyberluke
Copy link

Some further hints:
https://www.nuget.org/packages/Microsoft.IO.RecyclableMemoryStream/
https://adamsitnik.com/Array-Pool/ (this is like the FlyWeight design pattern I discussed before)
https://stackoverflow.com/questions/63363438/copying-pointers-data-to-byte-array-and-writing-to-memorystream-results-to-tons

This would help only with blocking GC call, but there are more performance issues on the road.

@cyberluke
Copy link

4:30 AM time for status. First lets have a look at this picture -->

solved

Process delay: 0ms

Looks like it is not working? But it is working! How? Cyberluke smashed keyboard randomly providing a series of illegal operations as well as random code blocks in memory deletion. Also had to write a little bit different VJoy C++ API.

Next time...try harder

@cyberluke
Copy link

cyberluke commented Aug 30, 2022

Probably solved Ramp Effect. There is a bug in SlimDX as several people on the internet thought. But nobody try to open DirectX SDK help or look at the SlimDX class.

So here is nice DirectX 9 page explaining even parameter range: http://doc.51windows.net/Directx9_SDK/input/ref/structs/dirampforce.htm

SlimDX and VJoy use int instead of long, but that should be ok as the range is from 0 to 10000. EDIT: the above link have incorrect character. Correct range is -10 000 to 10 000. You need to really download that Direct9 SDK and open *.chm help file included.

typedef struct DIRAMPFORCE {
    LONG  lStart;
    LONG  lEnd;
} DIRAMPFORCE, *LPDIRAMPFORCE;
typedef const DIRAMPFORCE *LPCDIRAMPFORCE;

This is SlimDX implementation:

	int RampForce::Size::get()
	{
		return sizeof( DICONSTANTFORCE );
	}

	void *RampForce::ToUnmanaged()
	{
		// Manual Allocation: released in Release function
		DIRAMPFORCE *result = new DIRAMPFORCE();

		result->lStart = Start;
		result->lEnd = End;

		return result;
	}

If you compare RampForce.cpp with ConstantForce.cpp, you see that RampForce should return:

	int RampForce::Size::get()
	{
		return sizeof( DIRAMPFORCE );
	}

This is probably some copy&paste error. Now I need to download three versions of DirectX SDKs, but my hybrid multiboot WinXp / Win10 machine is out of space, so I have to probably boot to Hirens Boot CD and change partition order then change partition size. Which means: backup data now :-)

@cyberluke
Copy link

cyberluke commented Aug 31, 2022

Few notes for @AndersMalmgren why not to move to latest .NET. In Czech, we call it "salamova metoda" or ham method. Each year they will push you a little with passive agressivity and you loose something. This year it is .NET 4.7. Next year, there will be only .NET 5.0, then 6.0. And voila! From superb project that can be used for many years, you will get Windows 11 or Windows 12 only app. Users need to buy new Windows to get legacy app running. It is a proven Apple and Microsoft business model.

Back to SlimDX:
Recompiled SlimDX in Windows XP (Visual Studio 2010). This is the reason why you don't want to bump artificially some SDK version like .NET 4 to 4.7! Why?

Because Windows 10 removed DirectX SDK - we know that.

And yes, it looks quite easy to just pull DX9 and XAudio headers via Nuget package. Plus installing XAudio2 runtime. Ok. But it still won't compile.

And guess what? It won't compile even as C++ package without .NET!

The reason is that Microsoft constantly keeps changing even old deprecated interfaces on a purpose. They just want to break compatibility on purpose to tell you: "You need to buy new computer!" Here is specific SlimDX issue: They completely changed several XAudio classes for Windows 10 SDK, even it is stable/deprecated.

They backported breaking changes.

Same as VCREDIST_X86 (Visual Studio 2015 C++ Runtime) - it should run on Windows XP and always did.

But THIS YEAR Microsoft removed a lot of DLLs on purpose even from 2010, 2015 C++ runtimes to remove WXP.

So it means, you install it from official MS website on WXP, but it still won't work! And you don't know why. Then you download VCREDIST_X86 from 2021, available only on Archive.org, and it start to work on Windows XP!!! It work always till few months recently they start removing DLLs even from outdated installers hosted on their website. They claim that SHA-1 signature is not supported anymore. But that does not mean they cannot host old files.

They just remove legacy download completely. So you will not find Windows 7.1 DDK (driver development kit) for VJoy, for example.

So on Windows XP, SlimDX compiles out of the box. On Windows 10, you would need to read ten pages on fan blog and REWRITE XAudio calls and parameters. This would on purpose only break Win XP, Win Vista, Win7 compatibility. While, if you not lazy developer, and you do your job properly, you will compile it on Windows XP (or Windows 7) and it will run on all OSes, including Win 10.

SlimDX: FIXED, they have pull request here: SlimDX/slimdx#515

Compiled Release DLL x86 is already in FreePIE. Same as new VJoy.

@cyberluke
Copy link

ramp

Ramp Effect working. Including parameters.

Tweaked also a few other parameters. It runs much faster if you disable Console.WriteLine() calls.

                Console.SetOut(System.IO.TextWriter.Null);
                Console.SetError(System.IO.TextWriter.Null);

cyberluke@1ba7049

@cyberluke
Copy link

cyberluke commented Aug 31, 2022

The Spring Effect force is not perfect yet. Sometimes it is delayed and jumps in force.

  1. After reading DirectX 9 SDK docs, each device reports what effects and what parameters it supports. At FreePIE VJoy start, it should query DirectInput (SlimDX) device for parameters and send them to VJoy, so VJoy can report it to the game and game can send correct parameter values and range.

  2. After analyzing logs, there is a lot of repeating calls. There should be optimization using some new PacketFilter class. This class will have different strategy per packet type. It will save last byte data for each packet type. And if exactly same bytes come multiple times, they get ignored, thus improving packet throughput. This way same effect with exactly same parameters will not get recreated. But on the other hand START, STOP and BLCKFREE command will always execute as you can replay already uploaded effect in device (furthermore START should always execute if effect duration != infinite).

  3. And there will be a pool of object instances for SlimDX Effect objects (and EffectParameters objects). Because C# is not good for this. You create like 100 objects in half second and what it will do: It will run Garbage Collector, which completely blocks all threads, including game, causing 200ms spike we can still see. If we keep rotating lets say an array of 10 Effect instances without creating new instances, GC will not even start. You have C# bells and whistles, but you need to type more code and optimize object creation only to get a simple algorithm running.

@AndersMalmgren
Copy link
Owner Author

AndersMalmgren commented Sep 1, 2022

@cyberluke I dont have time to maintain FreePIE, I put all my free time into a VR game project. https://www.youtube.com/c/MDADigital

Moving the GUI to .NET 6 shouldnt be a problem. The problem is that there is alot of Windows specific code pinvoke etc. I guess its possible with additional packages to get Windows speciifc features from full framework in .NET 6, I havent investigated what it takes.

            Console.SetOut(System.IO.TextWriter.Null);
            Console.SetError(System.IO.TextWriter.Null);

That disables output to the console. Which you dont want. Only write importanta stuff to the console. I think its mosly used to write warnings to the Error panel like

            if (!match)
                Console.Error.WriteLine("vJoy version of Driver ({0:X}) does NOT match DLL Version ({1:X})", driverVersion, apiVersion); 

Only use Console.WriteLine during development. When going live throw errors for the script to stop or Console.Error.WriteLine for warning

@CyberLuke-GBG
Copy link

Nice, I was making VR controllers and was on CES 2016 in LV. Here: https://www.facebook.com/realmagicvr/ and here: https://futurezone.at/digital-life/real-magic-haende-sollen-als-vr-controller-dienen/264.283.726 and smart VR shoe: https://www.youtube.com/watch?v=1fNNxPqh-S8&list=UUT7Ad5kcEYETdI914zGOKuQ ...the VR shoe would be able to compute rotation and relative position as well as precise pressure. Then there could be a kind of pressure based floor in VR arena for absolute position without camera. But it did not make any money and now I work as enterprise Java integration tech lead.

If you don't want to be a mantainer, I can help, at least for some time. It would be my pleasure! But I would preserve, not introduce any breaking changes because I think that is silly and not necessary.

You know, you don't understand: Nobody want .NET 6. Why to kill compatibility. After COVID retrogaming movement is raising. Win 11 will always run older .NET software, so it will stay compatible. Now it can still run on Windows XP. I buy a lot of Steam games, remakes, and GoG games. And they do not work on Windows 10. Like Mig-29 Fulcrum I am testing now. It does not work in fullscreen properly. Aquanox have issues with EAX sound, even I have three different Sound Blaster soundcards. So I have dual boot to WinXp and Win 10. On top of that I have 10 computers here including Voodoo 2 SLI and Pentium MMX.

@cyberluke
Copy link

  1. added method diagnostics.enable() & disable() for turning off console -> handy for switching from performance testing to debug during one session => cyberluke@bdb8fc4
  2. Console.WriteLine(something.ToString()) => this gets still evaluated and toString() methods do have performance impact => in my code, I have surrounded it with #if DEBUG and #endif directives, so it is removed during compile time if DEBUG directive is not set.

@AndersMalmgren
Copy link
Owner Author

  1. added method diagnostics.enable() & disable() for turning off console -> handy for switching from performance testing to debug during one session => cyberluke@bdb8fc4
  2. Console.WriteLine(something.ToString()) => this gets still evaluated and toString() methods do have performance impact => in my code, I have surrounded it with #if DEBUG and #endif directives, so it is removed during compile time if DEBUG directive is not set.

I'm not sure I like that, We shouldnt have any Console.WriteLines in the plugins once they are developed. Only one that should be writing to the console is the scripts using diagnostics.debug

@cyberluke
Copy link

I agree, but it was like that when I came here. On the other hand there will be always need to retest a code, so there needs to be something. I can get it outside of PR and use it only in my fork and focus on my fork only to get the job done.

@AndersMalmgren
Copy link
Owner Author

I agree, but it was like that when I came here. On the other hand there will be always need to retest a code, so there needs to be something. I can get it outside of PR and use it only in my fork and focus on my fork only to get the job done.

I'm of the school that you add such logging when you need it and remove it when you are done. Plus break points are better 9 times out of 10.
I checked the code and the only writelines we have today is in the vjoy plugin and they output to the error panel (warning about version diff).

But yeah like you said. I think it should be in another PR.

@cyberluke
Copy link

cyberluke commented Sep 1, 2022

If you get 5000 packets in 2 minutes, you really really do not want to set a breakpoint :-) sometimes you need to:

  1. analyze protocol
  2. profile

Debugger with breakpoint is if you looking for a specific exception or value of single element. Realtime communication is not that case.

I will not be adding and removing such logging every month.

Today, I look at several projects that use tracing and debug. They use pragma directive for compiler, so I've chosen that approach.

I respect your opinion, but I think it is not practical. It depends what kind of functionality are you developing. In VR you also cannot set breakpoint and wear helmet.

No, VJoy prints a lot of stuff to console. You can see it here in this PR, in the screenshots. It is not only warning and errors.

So I might remove diagnostics.enable() and diagnostics.disable() and continue working on my fork. You said you don't have time to mantain it here anyway. But I am keeping Console.WriteLine() and compiler directives in my code, namely #if DEBUG and #endif. The only thing I might change is the name 'DEBUG' to something else. I can change it to 'TRACE_ENABLE' for example because these are tracing outputs. That is commonly used in software development.

@AndersMalmgren
Copy link
Owner Author

AndersMalmgren commented Sep 2, 2022

If you get 5000 packets in 2 minutes, you really really do not want to set a breakpoint :-) sometimes you need to:

  1. analyze protocol
  2. profile

Debugger with breakpoint is if you looking for a specific exception or value of single element. Realtime communication is not that case.

I will not be adding and removing such logging every month.

Today, I look at several projects that use tracing and debug. They use pragma directive for compiler, so I've chosen that approach.

I respect your opinion, but I think it is not practical. It depends what kind of functionality are you developing. In VR you also cannot set breakpoint and wear helmet.

No, VJoy prints a lot of stuff to console. You can see it here in this PR, in the screenshots. It is not only warning and errors.

So I might remove diagnostics.enable() and diagnostics.disable() and continue working on my fork. You said you don't have time to mantain it here anyway. But I am keeping Console.WriteLine() and compiler directives in my code, namely #if DEBUG and #endif. The only thing I might change is the name 'DEBUG' to something else. I can change it to 'TRACE_ENABLE' for example because these are tracing outputs. That is commonly used in software development.

Vjoy plugin does not print to console in master. Must be debug code in that branch. Spraying with if debug statement will make code less readable. It's fine doing under development and even ok to check in under dev branch. Put i wouldn't accept a pull to master without debug code removed

@cyberluke
Copy link

Even VJoy kernel driver have this in source.

It is not if debug statement. It is compiler directive. That is something else.

I try to be helpful here and provide maximum verbosity, but I think our professional opinions will differ even more soon.

Thank you, I will not be doing any pull request then.

Have a nice day and good luck with your VR project.

@cyberluke
Copy link

If you generate empty project in Visual Studio (C++), it will do it for you in the same manner. So my proposal is industry practice.

// ConsoleApplication1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "ConsoleApplication1.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// The one and only application object

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{

@cyberluke
Copy link

  1. fixed polar coords

  2. discovered that flight stick might have Y axis on position 0 and X axis on position 1 after investigating VJoy kernel driver, virtual device and several real devices

  3. Logitech G940 does not have any performance issues, but original Microsoft Sidewinder Force Feedback 2 joystick shows increasing packet delay bug again. Probably less powerful MCU. But it works directly ingame.
    This leads me to idea that current Device.cs class in FreePIE responsible for integration with SlimDX needs a complete overhaul. The communication must be completely different. We are not using Effect.setParameters() now at all. I believe that instead of doing 'new Effect' and 'new EffectParameters' in SlimDX on every packet, there should be only one instance of Effect and on next EffectReportPacket we just call SetParameters on existing instance.
    Now because there is 'new EffectParameters' call on each new packet, SlimDX will scream that you cannot call setParameters on it, because it is different instance than being uploaded on device. Therefore all methods needs refactoring into one generic method that will be smart enough to only create new instances when truly needed.

  4. there needs to be some smart auto detection of axis at the beginning (you can print it out and you see axis [0] have different integer number (that's axis id) than vjoy axis [0]. Therefore they are reverted. Now you cannot rewrite VJoy kernel driver because that information is encoded in USB HID profile in vjoy.inx driver manifest. And that information is taken from Microsoft Sidewinder Force Feedback 2. You would need USB Hid analyzer, take it from the real device, and replicate that in VJoy driver. Current approach is keeping VJoy its specific axis features and properties and implementing a bridge between VJoy and real device. Apart from smart axis detection, you need to recalculate ConditionSet and put packet0 into condition[1] and packet1 into condition[0]. There is no other workaround, you cannot change the hardware definition (HID). Good is that most apps send polar coords. For other coords, you will need to change incoming and outgoing axis direction properties (X,Y) or X,Y,Z for spherical coords. Then there is like 10 other properties that can differ. Including range. So each device tells you its range in USB HID descriptor (or Windows registry) and you should recalculate VJoy range to your actual real device range. Good thing is that is some feature such as TriggerRepeatInterval is not present on target device and you send it, it gets simply ignored as defined in DirectX documentation (and it is true from the tests)

@cyberluke
Copy link

cyberluke commented Sep 8, 2022

New Device.cs (SlimDX integration) implemented: https://github.com/cyberluke/FreePIE/tree/Ffb

Now it does not ignore the first CreateEffect message that does not contain parameters. Because don't forget that FFB device is slow. This message allows to allocate memory and create new object inside FFB device.

So the flow is:

  1. CreateEffect
    ===> this gets sent to device
  2. SetEnvelope
  3. SetConstantForce or SetPeriodicForce (TypeSpecificParameters)
  4. Effect Report (contains direction, gain)
    ===> at this point we conctruct effect parameters from message 2, 3, 4, but we do not create effect anymore, we use SetParameters only. Now there is caveat. You need to set Flags there. If this effect is created for the first time, you set EffectParametersFlags.All.

Every other time, we get only:
3) SetConstantForce or SetPeriodicForce (TypeSpecificParameters)
4) Effect Report (contains direction, gain)
===> at this point we construct effect parameters from message 3 and 4 and we compare all individual values with previous values we have stored in memory in C#. For each changed parameter we add it to Flags. So it will look for example like this: Direction | Gain | TypeSpecificParameters or just Direction or just TypeSpecificParameters

This is how Microsoft designed this FFB protocol to be efficient for the target device. The target device updates only changed parameters thanks to this.

TODO:

  • right now for Direction changed parameter flag, I keep track only of Polar coords (which are most common, so it works), but I need to rewrite it to support other coords as well (Cartesian for now)
  • clean up a lot of messy code parts
  • think about class architecture redesign
  • add FreePIE API for users, who want to operate individual effects from within Python script (for example map FFB effect on a key stroke or integrate with 3rd party game API to send FFB effects based on ingame state, like weather condition for example)
  • VJoy is using USB HID of Sidewinder Force Feedback 2, but changes X and Y axes position - probably because they test it only on steering wheel. So Flightstick has different axes position than Steering Wheel => add some FreePIE method to changeAxisOrder. User, who wants steering wheel, keeps the default. User, who want flight stick will add changeAxisOrder() call in his script. On top of that I will add autodetection code, which will compare axis id from target device and from VJoy, so changeAxisOrder() method will be only if there is some unexpected bug in autodetection.
  • Need to retest on Sidewinder FFB 2, Logitech G940, Logitech Force Extreme 3D and Thrustmaster T300 Alcantara, Logitech RumblePad, Saitek force feedback gamepad (luckily have all of these)
  • rebuild VJoy kernel driver for Windows XP (Mostly all Steam games that are reedition of simulation games older than 5 years are fckd up and have various bugs on Windows 10)
  • make Youtube video to spread knowledge and help other people, who were asking for this for about 10 years on various forums for various devices (Assetto Corsa, Flight Sim, Novalogic sims, DCS World and older sims, HAWX, GRID, Jane's flight sims, etc.) => Youtube video will promote FreePIE, but will be focused on retrogaming community (Win10, WinXP, EIZO CRT monitor, 3d stereo glasses, Geforce 3 Ti, 2xVoodoo 2 SLI, R9 290X, Sound Blaster EAX)

@cyberluke
Copy link

Video of current progress, working in Windows 10: https://www.youtube.com/watch?v=pIR5bgpmE7E

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

No branches or pull requests

7 participants