Under Pressure

pressure_head

I started running Backworlds on a Surface Pro recently in order to determine how much work it would be to port the game to tablets – rather than the porting itself, the big challenges are how we change the controls to feel good on a tablet. The Surface Pro is practically a laptop running Windows 8 which meant I could immediately run the game, which was nice.

Since both of the Surface models come with a pressure-sensitive stylus this also gave me an excuse to connect this to the drawing – so if you are playing the game with a digital drawing tablet or display it would be more responsive. Implementing pressure-sensitivity with SDL in Windows wasn’t a straightforward affair though, so I thought I’d collect some of the things I learned in this post.

First things first – if you can get away with supporting only Wacom hardware, I would recommend simply implementing the Wacom SDK. It is very easy to use, I was up and running in less than an hour after just copying the code from their examples. I did have to remove the windows event queue integration and polled the events manually with WTPacketsGet, a similar structure to how SDL events are handled. Other than that, the only code I changed was to remove X,Y – coordinates from the packet data (as SDL gives us mouse position) and add PK_STATUS to determine if the eraser was used.

If you need to support more than just Wacom tablets, there are a few different interfaces. After looking around MSDN for a few hours I came upon some examples in the Windows SDK, most importantly an example using the RealTimeStylus (RTS) interface. This is not as straightforward as the Wacom API, but it is more versatile and has the added benefit of supporting multi-touch. The following demo will illustrate the basics of our RTS/SDL code:

pressure_app – source and executable to C++/SDL pressure-sensitivity test application

Essentially, the RTS interface involves creating a stylus object and tying that to a custom stylus plugin class that you create and override relevant functions in. The plugin class will then receive alerts when certain events (such as stylus up/down/move) occur and send you the event information (such as coordinates and pressure) in a sequence of unsigned integers. The Windows SDK example connects a renderer directly to the stylus interface but it was possible to remove the references to the render code and extract the pressure data manually.

The RTS stylus registers input for different tablet contexts depending on how you interact with it – with the Surface Pro it got three contexts, one for the pen, one for the touchpad and one if you were using your fingers on the screen. By using the stylus object, we can query the packet data format of each tablet context using GetPacketDescriptionData and figure out if there is any pressure data, where in the packet it is and what the max and min values are. Note that the stylus must be enabled before the packet description can be queried. We can then override the Packets function in the stylus plugin and use that knowledge to extract the pressure data from the packets we get (all of this is included in the example file).

We also need to connect the RTS object to a window – SDL can be told to pass through window messages by calling SDL_EventState, these can in turn give us a window handle that we can use to initialize the RTS. Be careful though, while SDL does pass through a WM_CREATE message it is not for the main window you are creating so if you pass that window handle to RTS creation it will fail. I opted to go for a lazy initalization routine with the WM_SETFOCUS messages instead and it worked fine, but there may be better ways.

This is an example showing the game running on a Surface Pro with touch controls and pressure sensitivity. Touch controls are nowhere near as good as real buttons/keys in terms of both latency and feedback so moving forward we will have to come up with more tablet-friendly ways of controlling the game without breaking it. If we do decide to do more work on that, I will write more about it at that time.

Again, here is a link to the demo application I wrote for RTS/SDL implementation

11 Responses

  1. Rafa says:

    Thank you. Very informative!

  2. Chris says:

    Hi, thanks for your article!

    I’m developing a sketching app using both RTS and Wintab, and am trying to do just what you did (I.e. use polling instead of the message queue). For some reason I can’t get the WTPacketsGet function to work, even though I more or less copied the PressureTest sample from the SDK . Everything initializes OK. Did you have to set any special flags or something ?

    Thanks,
    Chris

    • Anders Ekermo says:

      Apologies for not answering earlier, did not get a notification… Hopefully you’ve been able to work this out by now, but just in case;

      I, too, made sure this worked by changing the PressureTest example to poll the queue manually in the main program loop rather than in the window callback. I did not have any particular problem with that other than that the function pointer needed to be pulled from the DLL rather than the existing prototype used (since no static library handled linking). When we implemented it in the game I had one issue when accidentally using the wrong HWND to initialize WinTab, as mentioned in the blog post.

      … That might not be super helpful, let me know if you are still having issues and I can send you my modified test code.

  3. Henning says:

    Hi,

    Thanks for great blog post.

    I would love to take a look at your “pressure_app” but unfortunately the link seems to be broken.

    • Anders Ekermo says:

      Ah, good catch. We migrated the website a while back and it seems some of the files got lost in the shuffle. I reuploaded it to a different location and updated the links in the blog post, but just in case this is it: http://www.ekermo.se/proj/pressure_app.zip

      • Henning says:

        Thanks a lot!

        I am making a painting application called Leonardo: http://www.getleonardo.com

        Right now we only support the WinTab API. But I would like to add support for RealTimeStylus. As you say in the article it is more difficult, so I really appreciate you writing about it and posting the source code. Thanks!

        P.S. If you would like to try out Leonardo. Just shoot me an email and I can set you up with a life time license 🙂

  4. Jonathan says:

    Can’t thank you enough for that example code.

    I had forgotten how incredibly miserably COM in C++ is fighting with the RTS.

    • Anders Ekermo says:

      Glad it helped! And yeah, I’m glad the COM-programming I’ve had to do has been kept to a minimum.

  5. Wauw your the best, spend the whole afternoon trying to get so may samples up and running.
    Tried to integrate the wintab code in existing opengl app but got so many issues
    Your code works instant with just a small modification!

  6. Piero says:

    After almost 8 years I found your code very useful! I would have never reached my goal without it.
    Kudos

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.