Starcraft II on Linux: Pour me another glass of WINE

A long time ago, I'd heard of -- and briefly tried out -- a Windows compatibility layer for Linux called WINE. At the time, I didn't know much about Linux, or how such a compatibility layer might work. I just followed a bunch of recipes for getting Starcraft (the original, at the time) working on Linux like a magical incantation.

Fast forward to last week, and I once again had reason to try and make Starcraft (now II) work on a Linux laptop. Basically every component -- WINE, desktop Linux, Starcraft, and my own understanding of operating systems -- had hugely advanced since my first attempts. So I'm writing this as a mental bookmark for how I did it, and why I ended up choosing the solution I did.

This is a long post. I spend a lot of time talking about context and intermediate attempts, while skipping some of the more boring blind alleys I wandered down. To skip all of that and just see the answer -- that is, my ultimate recipe for making Starcraft II work on a modern Linux laptop, and perform at its best -- skip to the TL;DR.

Some context

Before talking about how I solve the problem -- that is, how I made Starcraft II work -- it's worth talking about some of the constraints on what I wanted to do.

The laptop isn't mine

The most interesting constraint, by far, is that I was working on a corporate laptop -- that is, a laptop that belongs to my employer. Said employer is extremely liberal regarding the uses to which its employees put the laptops to which it has been issued, so using mine to play a game in my spare time was totally kosher. Nonetheless, the mere fact that the laptop belongs to the company imposed a few constraints on the form my solution would take.

Policy constraints

  • No dual booting. The laptop is allowed to run the corporate Linux distribution, and that's it.
  • No local VMs. So "just" setting up a Windows VM to run Starcraft II was also not gonna happen.
  • No downloading random binaries from the internet. This is the most interesting constraint. First, it meant that using PPAs or other external repositories was way out of the question. Second, it meant that using a pre-made script, a WINE prefix manager with magic "make it run" buttons, or similar, was fraught with danger. I would only use one if I understood exactly how it worked.
    • Since, ultimately, running Starcraft II does involve downloading software from the Internet, "random binaries" deserves some qualification. In this case, the constraint is probably better interpreted as: binaries downloaded from the Internet should come from known, reputable sources. That means:
      • binary-only software should come from reputable publishers (and be digitally-signed as such),
      • open-source software that I didn't build myself should come from similarly reputable sources of pre-built open-source binaries, and
      • open-source software that I did build myself was built with a toolchain that was open to inspection.
    • A particularly gnarly version of this problem is winetricks. As a giant shell script, it's easily inspected. But many verbs download odd software from odd sources. I did end up using it, but with the limitation that I only used verbs that did pure configuration work (after reading their implementation), or verbs that downloaded software from authoritative sources. Since the main use of winetricks is for installing Microsoft libraries, that turns out not to be a very stringent limitation.

Engineering constraints

  • On corporate laptops, APT repositories other than the core operating system are banned.
  • Our corporate Linux distribution is an odd beast. It's... mostly Debian Testing. But there are some minor differences. Enough that PPAs -- which are usually targetted at Ubuntu distributions anyway -- or other external repositories probably wouldn't work. (It's also usually about 3 months out-of-date with respect to actual Debian Testing.)

I wanted to understand what I was doing

To be clear, I could, if I chose, have circumvented the technical restrictions, and ignored the policy considerations. It would have been far easier. (Technically, at least. Whether my employer would appreciate such deliberate circumvention, were they to discover it, is a separate question.) But it would also have made the problem far less interesting. This way, I really had to understand what I was doing.

The laptop itself

The laptop itself is a Lenovo X1 Carbon, 7th edition. It has a Core i7-8665U (Whiskey Lake) processor, (which includes an Intel UHD 620 GPU on-chip), 16GiB of RAM, and a 240GB NVMe SSD. Certainly powerful enough to run Starcraft II at reasonable settings.

So when going on a short trip across the country (during which I'd be working for a few days anyway), it was wildly preferable to just carry this ultraportable powerhouse, instead of also hauling along my Alienware 17 gaming laptop. (To be clear, I love all of my computing children, and the Alienware 17 is no exception. But it's heavy, and I'm lazy.)

The first attempt: the distro's wine packages

Most common Linux distributions have packages for WINE. I've attempted to use them many times over the years. For whatever reason, it never goes well. The packages are usually some combination of way out of date, and built with a funny/broken/too-conservative build configuration.

Unfortunately, Debian's wine packages had both problems. wine (the "stable" distribution) was version 5.0, while wine-development (the "development" distribution) was version 5.4. Upstream WINE, meanwhile, had just released version 5.14. (More on the build configuration below.)

I first tried "just" running wine Battlenet.exe to install the Battle.net client and download Starcraft II. This actually worked, up to a point: I got as far as Starcraft II completely downloading and installing. So far so good; let's fire it up! So I click "Play".

Nothing.

I mean, SC2_x64.exe is running, and pegging a core. For about half a minute. And then it dies with an incomprehensible stack overflow.

For the fun of it, I try out the 32-bit game binary. After all, 32-bit WINE is much more mature than its 64-bit counterpart. And it worked perfectly! So at least I had a functioning game.

What's ESYNC?

... or so I thought, until I actually tried to play the game. On the opening frame of any match, SC2.exe would freeze completely while pegging a core. After leaving it alone for a minute or two (on the hope that it would unblock itself), I admitted defeat, and started reading. One thing I kept finding references to was "WINE+ESYNC".

It turns out that "ESYNC" refers to a patchset for WINE that introduces a new implementation for Windows synchronization primitives, based on high-performance Linux ones. On a highly-multithreaded workload, like a modern game, such a change is likely to vastly improve performance. (Conversely, a slow implementation of synchronization primitives is essentially guaranteed to make the whole thing grind to a halt.)

Fortunately for me, the ESYNC patches were integrated into mainline WINE a while back. (Though I never did figure out in which version -- and, especially, if it was after the versions in Debian Testing.)

Unfortunately for me, ESYNC functionality is disabled by default on all current WINE builds. (Including, as far as I know, the current development version, 5.14.) In order to enable it, you need to set WINESYNC=1 in wine's environment.

One build of WINE later (more on actually building WINE below), 32-bit Starcraft II actually worked perfectly.

The thing about modern games, though, is that they're very much equipped to take advantage of large amounts of memory. And my laptop had large amounts of memory. So running a 32-bit binary is a nice fallback that means I can actually play Starcraft. But, in the immortal words of Winifred Sandersonwe can do better.

UPDATE:

This entire section turns out to be completely false. The ESYNC patches were never integrated into upstream WINE. The WINESYNC=1 I've been putting in my launcher script does absolutely nothing. I can only guess that what made everything work was just using a newer version of WINE.

Depending on my mood over the next couple of weeks, I may try to rebase the ESYNC patchset onto WINE 5.14 and see if it makes any difference. But that's... substantial work.

UPDATE 2:

Turns out the ESYNC patches made their way into staging in April 2019, and are in fact in staging v5.14. However, the patchset is "disabled" (not applied automatically), and is therefore absent from my own WINE 5.14 staging builds. (Also, as it turns out, it does respect the WINESYNC environment variable. So at least that part works the way I expected it to. Except that it's actually called WINEESYNC...) Unfortunately, it hasn't been rebased against mainline WINE since May 2020, so the rebasing work from above largely remains.

UPDATE 3:

The last working version of the ESYNC patchset is in staging commit 4e692b5301c683fef8fc203eda008be7f04b5871 (which applies to WINE commit e48fabff525061c8eea9558084a97308cebe6b7b, from late May 2020). I tested that out today, with an otherwise identical build configuration to the staging-5.14 builds I've been using to date. It yields a modest, but noticeable, performance improvement: CPU utilization dropped by about 10-15 percentage points, and frame rate went up by about 10%.

Stack overflow?

A little Googling revealed a WINE bug that describes exactly what I was experiencing: a variety of 64-bit binaries, explicitly including Starcraft II, crashing soon after launch with a stack overflow. Unfortunately, I... mostly had no idea what the folks on that bug were talking about. I had two questions that were critical to my understanding what was going on:
  1. What is "WINE staging"? A brief search through the Debian package repository revealed nothing even remotely relevant.
  2. What does "build WINE with PE support" mean? PE (Portable Executable) is the executable file format for Microsoft Windows, much like ELF is for Linux. WINE is entirely for running Windows applications on Linux, surely that means it always has PE support?

Stage? What stage?

Fortunately, the answer to the first question is pretty straightforward. WINE source code actually lives in two Git repositories. The main source code lives in https://source.winehq.org/git/wine.git. However, the WINE developers also have a separate repository of patchsets called "staging"; as far as I can tell, these are ideas that they're throwing around, but aren't yet ready for inclusion in mainline WINE. That repository is https://github.com/wine-staging/wine-staging. So when people talk about "staging", they're talking about a build of WINE containing the staging patchsets.

PE support

The "PE support" question is somewhat more complex, and I admit to still not fully understanding it. However, the core point is that it's a bit of a misnomer. As I had intuited, WINE always has "PE support", in that it always supports launching PE binaries. But when it comes to DLLs, more interesting things start to happen.

It turns out that WINE, much like Windows itself, implements a lot of functionality as dynamically-loadable libraries. Those libraries can be in either PE or ELF format. If mingw (a variant of GCC that can build for Windows; it's packaged for most Linux distributions) is not available, they will be built as ELF-format shared libraries; if it is, they will be built as PE-format DLLs.

Fixing the bug

The part I don't fully understand is why this matters. However, I can assure you that it does. The following is my rough guess, based on my reading of https://bugs.winehq.org/show_bug.cgi?id=45349 (and the 32-bit form of the same issue, https://bugs.winehq.org/show_bug.cgi?id=21232):
  • Some applications hook syscall attempts. That can be an anti-cheating mechanism (in the case of games), or a security mechanism (in the case of the Chrome sandbox), among other possible uses.
  • "Hooking" a syscall means replacing the entry in the NTDLL.DLL call table with a call to a different function, storing the replaced entry elsewhere in memory. (NTDLL.DLL is the Windows DLL containing syscall stubs.)
  • When the application tries to invoke the syscall in question, the hook function is called. It will execute, and then call the original code that it stored in memory.
  • NTDLL.DLL call table entries on Windows have a very specific "shape" -- they are always less than 16 bytes long. So applications that hook syscalls generally copy that amount of data. (At a guess, DLL entries can probably be either function pointers or inline code, and the Microsoft NTDLL.DLL ones are usually very short inline code.)
  • WINE NTDLL.DLL call table entries can be longer than 16 bytes. (At a guess, also inline code, but more of it.) When an application hooks such an entry, it ends up truncating code, sometimes even in the middle of an instruction. That's... not good.
  • The solution is to create thunks for those NTDLL.DLL call table entries, making sure the thunk entries are always less than 16 bytes. (At a guess, either by making them function calls instead of inlining them, or by ensuring they're inlined call instructions.)
  • For some reason, the staging patch only causes those thunks to be generated when WINE libraries are in PE format.
That's a rough guess. If anyone reading this understands what's actually going on, I'd be thrilled if you'd leave a comment describing it to me in more detail!

However, it does mean that fixing the bug is straightforward: build WINE 5.14 with staging patch winebuild-pe_syscall_thunks. (I actually built with all staging patches, but that's the only one needed to get Starcraft II working.)

At this point, we now have Starcraft II 64-bit working. So from here, I can actually play Starcraft, in 64-bit mode! It all works. It's all stable. It can use as much RAM as it wants. And the graphics performance is... OK. Once again, we can do better.

(Once again, more on actually building WINE below.)

Aside: on winetricks and magic

If you've Googled around for how to get various games working in WINE, there's a lot of talk about using winetricks scripts to install various libraries, set specific Windows versions, and so on. Particularly popular are the following kinds of winetricks verbs:
  • winxp/win7/win10 -- to set the emulated Windows version.
  • corefonts/allfonts -- to install fonts that are lacking from the default install.
  • gdiplus/gdiplus_winxp -- install Microsoft GDI+ library.
  • directx9 -- to install Microsoft DirectX 9.
  • d3dxNN -- to install Microsoft D3DX (Direct3D Extension) libraries. These are usually math libraries or similar, implemented on top of the Direct3D base APIs.
  • d3dcompiler_XX -- to install the Microsoft HLSL compiler.
  • vcrun2005/vcrun2008/... -- install various Microsoft runtime environments.
Particularly in the "olden days" of WINE, many of these were necessary to work around bugs or missing features in WINE itself. These days, they turn out not to be necessary; WINE's built-in components mostly work pretty well. However, a lot of HOWTOs and install scripts still parrot them.

I did end up using the win10 verb from winetricks, just because it's quicker than clicking around in winecfg. I also used the corefonts verb, though later reading has made me think this method is probably preferable. (I could see, though, how d3dcompiler_XX might also be worthwhile. Ditto d3dxNN, since the D3DX libraries are all support functionality anyway.)

Modern graphics APIs: to Vulkan, and beyond!

A modern game's performance is almost entirely related to its graphics subsystem. There are whole books to write on game engine optimization, choosing a GPU, using the right GPU drivers on your distribution, and so on. But this isn't one of those books; suffice it to say that I didn't write the game, I can't change the GPU in a laptop, and I'm using the best Intel GPU drivers available to me.

As it turns out, what I can control is the graphics API.

There are three 3D graphics APIs in common use:
  • OpenGL -- well-respected cross-platform graphics API; traces its history back to Silicon Graphics in the 90s.
  • Vulkan -- high-performance, cross-platform graphics API designed for modern hardware and software; performs particularly well in a multithreaded environment.
  • Direct3D -- Windows-only graphics API, written by Microsoft as part of DirectX. The versions in common use are:
    • Direct3D 9 -- the last version of DirectX available separately to the operating system. That download was available for Windows XP, and ships as part of all subsequent Windows OSes.
    • Direct3D 10 -- shipped as part of Windows Vista, and all subsequent Windows OSes. Not particularly widely-used.
    • Direct3D 11 -- shipped as part of Windows 7, and all subsequent Windows OSes.
    • Direct3D 12 -- shipped as part of Windows 10, and all subsequent Windows OSes. Very new; not particularly widely-used yet.
Vulkan is, in a sense, the direction that the world is headed. But, naturally, most Windows games are based on DirectX. Making it available on Linux is therefore the chief engineering barrier to running Windows games on Linux.

There are four libraries available for doing this under WINE, with somewhat-overlapping sets of functionality:
  • WineD3D. WINE's own implementation of Direct3D 9, 10, and 11. Library functions (like vector math and HLSL compilers) were simply rewritten; functionality requiring hardware access was implemented by translating to equivalent OpenGL functionality. This is the lowest-performance route, mainly due to its use of OpenGL, but also the lowest-friction one.
  • Microsoft DirectX. A lot of guides on the Internet suggest doing this. Of course, installing all of Microsoft's DirectX won't work, since the components requiring hardware access are all written for Windows. However, installing the Microsoft versions of various support libraries (math, shader-language compilers, and so on) can often help with both stability and performance, since these are the libraries that games are actually written for. (This is what the d3dxNN and d3dcompiler winetricks verbs do.)
  • VKD3D. A library by the WINE developers, which WINE uses (if built --with-vkd3d) to translate Direct3D 12 calls to their Vulkan equivalents. Not directly useful for Direct3D 11 or lower, which is what Starcraft II uses.
  • DXVK. The equivalent of VKD3D for Direct3D 9, 10, and 11 -- it translates hardware operations into Vulkan. Under good conditions, performance can be as good as "native" (ie running in Windows on the same hardware).

Onwards to DXVK

WineD3D is the "do nothing" solution to this problem -- if no other action is taken, the Direct3D implementation in use is the one Wine supplies. I thought the performance I was experiencing in Starcraft II was a little subpar, so I decided to try out DXVK instead.

The source was easy:
$ git clone https://github.com/doitsujin/dxvk

The DXVK build instructions are foolproof, and spell out what dependencies are required:
$ sudo apt-get install glslang-tools
(We already installed all of the others -- mingw and some multilib things -- earlier on.)

Those build instructions are actually so foolproof that I won't reproduce them here, other than to remark that you need to build both the win32 and win64 variants.

You'll end up with 6 DLLs in each build:
  • dxgi.dll
  • d3d9.dll
  • d3d10.dll
  • d3d10_1.dll
  • d3d10core.dll
  • d3d11.dll
I then installed them in my WINE prefix. The ones from build-win64 go in $WINEPREFIX/drive_c/windows/system32; those from build-win32 go in $WINEPREFIX/drive_c/windows/syswow64. (I usually preserve the old copies, with a .old suffix.)

Finally, I manually marked those DLLs as "native" (instead of "builtin") in winecfg for that prefix. (In the DXVK repository, there's a script for doing this automatically, but I elected not to use it.)

And the performance improvement I got was... modest. But a lot of things looked better. Little effects whose absence I hadn't noticed were... noticeable in their newfound presence. It wasn't the landslide victory I was hoping for, but I'll take it.

Building WINE

Building DXVK was actually the last thing I did; by the point I was trying that, I was already proficient at building WINE. But I've left the WINE build description to the end because building WINE is something of an adventure.

How many bits?

The first thing to understand is that you'll be building two copies of WINE: 32-bit and 64-bit. (So for all of you "AMD64 or die" Linux purists... sorry, you're gonna have to lose that impulse.) The reason is that modern Windows is very much a mixed environment, and therefore so is most Windows software. You therefore need both environments to work.

Doing this is called a "WoW64 build", named for the Windows subsystem that enables this mixed environment. To accomplish this, you end up building two separate copies of WINE: one for i386, one for amd64. You'll build the 64-bit (amd64) copy first, and then tell the 32-bit (i386) buildsystem where to find it. This sounds a little convoluted, but turns out to be pretty simple to do. And as with most things WINE-related, the build instructions are excellent.

... once you have the appropriate tools installed. To get those tools, on the other hand, required a little knowledge.

First, we're building software, so we'll need some compilers:
$ sudo apt-get install build-essential gcc g++ automake autoconf gcc-multilib g++-multilib
The multilib packages allow 64-bit compilers to build 32-bit code.

Second, we know from before that we'll need MinGW, so that WINE DLLs get built in PE format:
$ sudo apt-get install mingw-w64 mingw-w64-tools

Dependencies

WINE has a long list of optional features, and it's not always clear which features are going to be required by the application you want to run. The WINE build instructions are excellent, if not entirely exhaustive, regarding those options. But truly understanding the list of inclusions and exclusions you need is still extremely laborious.

I took the approach of "start with Debian, tweak from there, and assume you want most things to be on". Unfortunately $ apt-get build-dep wine didn't work for me; I had to do this myself. The Build-Depends from the wine package did give me a head start ($ apt-cache showsrc wine |grep Build-Depends). But this still needed some thought.

As with most software, WINE's build dependencies fall into two categories: tools used in the build process, and libraries against which the program will be built. In the "tool" category were things like flex, bison, lzma, quilt, and similar. I could just install the amd64 versions of those, and that was it. In the "library" category is basically everything else. These generally have package names that look like libXXX-dev, and you generally need them installed separately for each architecture you're building for -- in my case, both i386 and amd64.

A rather unpleasant shell pipeline later, I'd split the dependencies into those two categories, filtered out any non-linux and non-x86 dependencies, and installed all of the tools and libraries I was going to need. Now I can think a little bit, and use the table in the WINE build instructions to add things that are absent, and pare out things I wasn't going to need.

UPDATE:

There's a much simpler way; the dependencies of wine-development turn out to be much less convoluted than those of wine. So for the amd64 dependencies, I can just do
$ sudo apt-get build-depends wine-development
The i386 dependencies are a little more involved, but the rather unpleasant shell pipeline from above can be reduced to the slightly less unpleasant
$ apt-cache showsrc wine-development | grep Build-Depends: | sed -e 's/^Build-Depends: //'| sed -e 's/, /\n/g' | sed -e 's/ (.*)$//' | grep -- '-dev$' | sed -e 's/$/:i386/' | xargs sudo apt-get install -y
This roughly translates to "find everything in the build dependencies for wine-developement, filter out only library-header dependencies, and install the i386 versions of all of them".

I then remove everything related to VKD3D, because like WINE itself, the packages in Debian are too old to be useful. (As you'll see below, I actually didn't end up using VKD3D at all, but if you do, you'll want to build it from source.)
$ sudo apt-get remove --purge libvkd3d-dev libvkd3d-utils1 libvkd3d1 libvkd3d-dev:i386 libvkd3d-utils1:i386 libvkd3d1:i386

Getting the source

Of course, you'll also need the WINE source code, as well as the source code for the staging patchset. I built the 5.14 versions of both.
$ git clone git://source.winehq.org/git/wine.git; (cd wine && git checkout wine-5.14)
$ git clone https://github.com/wine-staging/wine-staging; (cd wine-staging && git checkout v5.14)

Staging comes with a handy script for applying all of the patches, in the correct order, to the WINE source tree.
$ ./wine-staging/patches/patchinstall.sh DESTDIR="`pwd`/wine" --all

I then committed the patched source to a local branch, to save myself some trouble while experimenting.

Putting it all together

Once you have the source code checked out and the dependencies figured out, building WINE is pretty straightforward. Since I was experimenting with several build configurations, I wrote a little script to help me out, but if you're following the (once again, excellent) build instructions it basically looks like:

$ export BUILD64_DIR="`pwd`/build64"
$ export BUILD32_DIR="`pwd`/build32"
$ mkdir build32 build64
$ (cd build64 && ../wine/configure --enable-win64 && make -j6)
$ (cd build32 && PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig ../wine/configure --with-wine64="$BUILD64_DIR" && make -j6)

The WINE configure script is adept at detecting available libraries, and building against them if they're available (or dropping a feature if not). But there are certain features I didn't want to rely on the autodetection for, and instead enforce that they must be present (or absent).

Here are the flags I ended up using, and why I used them:

--without-alsa --without-oss --with-pulse: I'm using GNOME on Wayland, so I wanted to make sure the audio went through the (GNOME-managed) PulseAudio path. Forcing a build that only has PulseAudio support was a simple way to do that.

--with-x: Call me paranoid; I wanted to force GUI X support.

--with-mingw: This was a way of checking that I had the MinGW tools installed correctly. It forces a PE build, rather than an ELF build, for the WINE DLLs.

--with-vulkan: Even though I'm not using wined3d's Vulkan support, WINE still needed to be built with Vulkan support so that DXVK could work.

--without-vkd3d: I couldn't get VKD3D to work to my satisfaction. Most git revisions wouln't build, with a link error I didn't understand. The few that did failed their tests, indicating incorrect behavior. If I knew more about these APIs, I might have been able to either fix those, or recognize that the test failures were spurious / expected. But for now, Direct3D 12 isn't widely used anyway; I'll disable support for it until I know more, or development has progresses. (UPDATE: I think the link errors were a result of some subtle mistake I was making. So I can now get VKD3D to build without trouble. However, I still see the test failures, and installing DXVK risks breaking VKD3D if done wrong anyway.)

The result:

$ WITH="--without-alsa --without-oss --with-pulse --with-x --with-mingw --with-vulkan --without-vkd3d"
$ export BUILD64_DIR="`pwd`/build64"
$ export BUILD32_DIR="`pwd`/build32"
$ mkdir build32 build64
$ (cd build64 && ../wine/configure $WITH --enable-win64 && make -j6)
$ (cd build32 && PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig ../wine/configure $WITH --with-wine64="$BUILD64_DIR" && make -j6)

TL;DR

My recipe for getting Starcraft II working on a 2019-era Linux laptop with a Vulkan-compatible GPU and the then-current version of WINE: (all command snippets occur in the same directory; I used ~/wine)
  1. Get the v5.14 WINE source code: (replace wine-5.14 below with e48fabff525061c8eea9558084a97308cebe6b7b if you want to use the last working ESYNC patches)
    $ git clone git://source.winehq.org/git/wine.git; (cd wine && git checkout wine-5.14)
  2. Get the v5.14 WINE staging patchsets: (replace v5.14 below with 4e692b5301c683fef8fc203eda008be7f04b5871 if you want to use the last working ESYNC patches)
    $ git clone https://github.com/wine-staging/wine-staging; (cd wine-staging && git checkout v5.14)
  3. Apply the WINE staging patchsets:
    $ ./wine-staging/patches/patchinstall.sh DESTDIR="`pwd`/wine" --all
    (I also committed this to a branch, to save myself some trouble when experimenting.)
  4. Install development tools:
    $ sudo apt-get install build-essential autoconf automake gcc gcc-multilib g++ g++-multilib
  5. Install WINE's build dependencies. I don't have a good, simple command for that; have a look above for how to approach this task. Remember that you need both the AMD64 and i386 library dependencies and development headers.
    UPDATE: This worked for me
    $ sudo apt-get build-depends wine-development
    $ apt-cache showsrc wine-development | grep Build-Depends: | sed -e 's/^Build-Depends: //'| sed -e 's/, /\n/g' | sed -e 's/ (.*)$//' | grep -- '-dev$' | sed -e 's/$/:i386/' | xargs sudo apt-get install -y
    $ sudo apt-get remove --purge libvkd3d-dev libvkd3d-utils1 libvkd3d1 libvkd3d-dev:i386 libvkd3d-utils1:i386 libvkd3d1:i386
  6. Install MinGW:
    $ sudo apt-get install mingw-w64 mingw-w64-tools
  7. Configure MinGW to use threads:
    $ sudo update-alternatives --config x86_64-w64-mingw32-gcc
    $ sudo update-alternatives --config x86_64-w64-mingw32-g++
    $ sudo update-alternatives --config i686-w64-mingw32-gcc
    $ sudo update-alternatives --config i686-w64-mingw32-g++

    (For each one of these, pick the posix variant.)
  8. Pick your WINE build configuration flags. Mine were:
    WITH="--without-alsa --without-oss --with-pulse --with-x --with-mingw --with-vulkan --without-vkd3d"
  9. Build WINE. You'll need a WoW64 build, because most/all Windows software -- and especially games -- have both 32-bit and 64-bit binaries. Once again, the WINE build instructions are excellent, but here's the shortcut:
    $ export BUILD64_DIR="`pwd`/build64"
    $ export BUILD32_DIR="`pwd`/build32"
    $ mkdir build32 build64
    $ (cd build64 && ../wine/configure --enable-win64 $WITH && make -j10)
    $ (cd build32 && PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig ../wine/configure --with-wine64="$BUILD64_DIR" $WITH && make -j10)
  10. You now have have a functioning build of WINE. You can make install to put the build results where they're visible to the rest of the system, or add --prefix=/where/you/want to the two configure invocations above to pick a custom install directory. I did neither; instead I just add the build directories to my PATH when running WINE.
  11. Create a prefix and install Starcraft II.
    $ export WINEPREFIX=~/.local/share/wineprefixes/sc2"
    $ export WINEESYNC=1
    $ wine winecfg

    (Set the Windows version to win10.)
    $ wine Battlenet.exe
    (Install Starcraft II from the Battle.net client.)
  12. At this point, everything works. You can stop here, or keep going if you really care about performance. Just make sure you always run wine with WINEESYNC=1 in its environment
  13. Get the DXVK source code:
    $ git clone https://github.com/doitsujin/dxvk
  14. Install DXVK's build requirements:
    $ sudo apt-get install meson
  15. Build DXVK for 32-bit and 64-bit, following the instructions in its README.md:
    $ meson --cross-file build-win32.txt --buildtype release build-win32
    $ meson --cross-file build-win64.txt --buildtype release build-win64
    $ (cd build-win32 && ninja all)
    $ (cd build-win64 && ninja all)
  16. Install the DLLs into your WINEPREFIX. There are 6 of them. The ones from build-win64 go in $WINEPREFIX/drive_c/windows/system32; those from build-win32 go in $WINEPREFIX/drive_c/windows/syswow64. (I usually preserve the old copies, with a .old suffix.)
    The 6 DLLs are:
    • dxgi.dll
    • d3d11.dll
    • d3d9.dll
    • d3d10.dll
    • d3d10_1.dll
    • d3d10core.dll
  17. Tell WINE to use the DXVK DLLs:
    $ wine winecfg
    (Go to the Libraries tab. For each of the 6 DLLs, add an override and set it to "native".)
  18. You're done! Remember to always set WINEESYNC=1 in the environment before running wine, if you're using an ESYNC build.

Looking to the future

I plan to run a few other tests in the near future.
  1. I want to switch to a --with-vkd3d build of WINE. Direct3D 12 isn't particularly widely-used right now, but its usage will only grow with time.
  2. Now that I understand what the d3dx libraries are for, I want to experiment with installing the Microsoft versions of a few more. I particularly want to see how they perform in combination with DXVK.
In the longer term, the future of WINE looks super interesting. WINE remains under very active development, particularly on the implementations of various versions of Direct3D. I'm very interested in the future of two projects:
  • VKD3D. As Direct3D 12 becomes more popular, enabling this feature will become unavoidable. That's not a bad thing; Direct3D 12 and Vulkan supposedly share a lot of ideas, and both are designed to minimize CPU overhead. I think there's a lot to look forward to in this space.
  • WineD3D Vulkan renderer. I mentioned earlier that WineD3D translated Direct3D 9, 10, and 11 calls into OpenGL. The WINE developers are currently working on a Vulkan-based renderer instead. That project is in very early development, but when it comes to fruition, DXVK will no longer be necessary. Which... should mean that no native DLLs would be needed to run Starcraft II, among (ideally) many other games.

Credits

I've summarized lessons I've learned from a bunch of places in this post. Hat-tips to the following particularly critical pages:

Comments