Given that I’d like to move to Linux permanently some time, I want to get a deeper understanding of Wine. Please note this is not a tutorial, more like an adventure, so if you’re looking for actual instructions, you may need to dissect this post yourself.
Near the beginning of the year, I had tried to download and play a game called Foxhole on my M2 Mac. However, this game only supports Windows. Having ditched Windows over 4 years ago, I didn’t want to go back just to play, so I downloaded the Crossover free trial and gave it a go.
And it worked pretty great! While playing, my Mac ran pretty hot but that’s totally expected considering there’s Wine + x86 emulation + DirectX graphics tr…
Given that I’d like to move to Linux permanently some time, I want to get a deeper understanding of Wine. Please note this is not a tutorial, more like an adventure, so if you’re looking for actual instructions, you may need to dissect this post yourself.
Near the beginning of the year, I had tried to download and play a game called Foxhole on my M2 Mac. However, this game only supports Windows. Having ditched Windows over 4 years ago, I didn’t want to go back just to play, so I downloaded the Crossover free trial and gave it a go.
And it worked pretty great! While playing, my Mac ran pretty hot but that’s totally expected considering there’s Wine + x86 emulation + DirectX graphics translation all happening at the same time.
I didn’t note the FPS but it must have been at least 40, it felt smooth. I have always been used to slideshow-quality games on potato computers so this was pretty amazing. However, the only issue but big issue was that once every 15 to 60 seconds, the game would stutter. I couldn’t mentally get past the stutters so I had to put down the game.
can I fix it?
Fast forward almost a year, I am back wondering how I can fix the stutters. With no Crossover trial days left, and now that Whisky is now no longer maintained, and also boredom, it is time to try building Wine myself on MacOS so maybe I can find and fix the source of the stutters??
Wine has a nice Wiki page on how to build wine, including specific MacOS instructions here.
Note that Crossover does release the source code for their software but I am trying to build the original Wine code that is actively under development, available here.
let’s go!
On the MacOS specific page, they ask you to pick and stick with either Homebrew or MacPorts for dependencies. I went with Homebrew since I’m used to it.
| ``` | |
| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 |
|
build time dependencies
brew install –formula bison mingw-w64 pkgconfig
run time dependencies
brew install –formula freetype gnutls molten-vk sdl2
At this point, the docs sort of diverge a bit, seems like this part of the Mac specific wiki page hasn't been maintained recently, so I had to go back to the main page for the rest of build instructions\. Note that I had to add bison to my $PATH \(which is noted in the Mac specific wiki page\)\.
I know MacOS got rid of 32-bit starting in Catalina, so I tried to look for 64-bit specific instructions\.
I found instructions like this:
> If you're on a 64-bit system, all you need to do to build a 64-bit wine is pass --enable-win64 to the configure script when you run the above commands:
>
>
> | | |
> | - | - |
> | ```
> 1
>
> ``` | ```
> ../wine-source/configure --enable-win64
>
> ``` |
>
> The problem is that by itself, that build will only run applications compiled for 64-bit Windows\. Since the vast majority of Windows applications are 32-bit, you very likely want to follow the WoW64 instructions below\.
Good job me\!
Looking at the WoW64 section, the gist is:
1. I have to build the 64-bit version in one build directory\.
1. Then I have to build the 32-bit version of Wine in a *separate* build directory\.
From here, I had to fix a few problems before compilation succeeded\.
1.
`brew install llvm lld` for clang, since Apple clang doesn't support building Windows-style executables
1.
Add `--without-pulse` to the configure command, it's Linux specific\. Symptom was `error: use of undeclared identifier 'PTHREAD_MUTEX_ROBUST'` when compiling the file `pulse.c`\.
1.
Add the following CPPFLAGS and LDFLAGS and re-configure\. It is for all the Homebrew dependencies as well as X11 via XQuartz\.
| | |
| - | - |
| ```
1
2
``` | ```
export CPPFLAGS=" -I/opt/homebrew/include -I/usr/X11/include "
export LDFLAGS=" -L/opt/homebrew/lib -L/usr/X11/lib"
``` |
Stepped away for less than 15 minutes and it was done\. I know I haven't done the 32-bit build yet but I just want to see if it'll do something \(like fail spectacularly when loading 32-bit libraries\)\.
| | |
| - | - |
| ```
1
2
3
4
``` | ```
build-wine64 % ./wine
Usage: wine PROGRAM [ARGUMENTS...] Run the specified program
wine --help Display this help and exit
wine --version Output version information and exit
``` |
Awesome\!
| | |
| - | - |
| ```
1
2
``` | ```
build-wine64 % ./wine ~/Downloads/SteamSetup.exe
zsh: killed ./wine ~/Downloads/SteamSetup.exe
``` |
Hmm, what?
## here's what I did wrong
Before I go too deep, I want to note that Wine works quite well out of the box when using Gcenx's build of Wine offered on Homebrew, henceforth known in this post as \"wine-stable\"\. To install it, simply run:
| | |
| - | - |
| ```
1
2
3
4
``` | ```
brew install wine-stable
# ready to go!
wine <exe>
``` |
So I am not complaining about lack of alternatives or the difficulty of setting things up\. In fact, it is a bit too simple nowadays, that I wouldn't really know how to fix things myself\. So part of this adventure is getting to know Wine better so I can at least try and resolve issues myself\.
Back to the zsh killed error, I tried searching online for info about this error\. From what I read on the Wine forums, the summary is that it's due to the fact that Wine was built as an arm64 executable, but I couldn't figure out *why* that was a problem\. Like, my Mac supports arm64 instructions, and can emulate x86 instructions, so what is the technical barrier here? Running `file` confirms it is indeed arm64\.
| | |
| - | - |
| ```
1
2
``` | ```
build-wine64 % file ./wine
./wine: Mach-O 64-bit executable arm64
``` |
Forcing it to run as x86\_64 with `arch -x86_64 ./wine` doesn't help either \(I tried this if for some reason Wine was using some special x86-64 assembly\)\.
If you have any ideas, I would appreciate the insight\! My email is linked at the bottom of this page\!
## trying again
Homebrew has the nice feature of separating packages into different directories depending on the architecture\. But we have to get the x86 version of Homebrew first\.
| | |
| - | - |
| ```
1
2
3
4
5
6
7
8
``` | ```
# install Rosetta 2
softwareupdate --install-rosetta --agree-to-license
# start shell as the x86 version
arch -x86_64 zsh
# install x86 homebrew because it auto-detects
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
``` |
I also found this [helpful snippet online](https://stackoverflow.com/a/68443301) that automatically sets up the Homebrew environment based on your shell's detected architecture:
| | |
| - | - |
| ```
1
2
3
4
5
``` | ```
if [ "$(arch)" = "arm64" ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
else
eval "$(/usr/local/bin/brew shellenv)"
fi
``` |
It was also at this time I decided to consult Gcenx's build of Wine to see what configure options they used\. Link [here](https://github.com/Gcenx/macOS_Wine_builds)\.
I added the `--prefix` option and the `-C` flag \(for configure result caching\) but the rest is copied verbatim\.
| | |
| - | - |
| ```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
``` | ```
../wine-src/configure -C \
--prefix=$(pwd) \
--build=x86_64-apple-darwin \
--enable-archs=i386,x86_64 \
--disable-winebth_sys \
--without-alsa \
--without-capi \
--with-coreaudio \
--with-cups \
--without-dbus \
--with-ffmpeg \
--with-freetype \
--with-gettext \
--without-gettextpo \
--without-gphoto \
--with-gnutls \
--without-gssapi \
--with-gstreamer \
--with-inotify \
--without-krb5 \
--with-mingw \
--without-netapi \
--with-opencl \
--without-opengl \
--without-oss \
--with-pcap \
--with-pcsclite \
--with-pthread \
--without-pulse \
--without-sane \
--with-sdl \
--without-udev \
--with-unwind \
--without-usb \
--without-v4l2 \
--with-vulkan \
--without-wayland \
--without-x
``` |
Few important things to note here:
-
the Mac install instructions required us to install an X11 server but there is a `--without-x` flag?
-
The configure script's contents imply that there exists a `winemac_drv` which seems to replace the X server functionality, but only for Mac\. This seems to be a new feature that the Mac docs don't reflect
-
Nevertheless, I've kept the CFLAGS and LDFLAGS for X11
-
coreaudio is Mac specific audio APIs so that is why we've disabled pulse & alsa as those are Linux specific
-
`--without-opengl` and `--with-vulkan` so we should ideally be using Vulkan at all times\. But not really sure how this works given that not all applications will try and use Vulkan 🤔\.
- Provided by the
`brew install molten-vk`
we did at first for dependencies
-
`inotify` is **not** on Homebrew, I incorrectly mixed it up with [libnotify](https://formulae.brew.sh/formula/libnotify) \(sorry I was desperate to get it working\)
-
To install it, you'll need the BSD alternative to inotify, [libinotify-kqueue](https://github.com/libinotify-kqueue/libinotify-kqueue)
-
Because I started with a fresh x86 Homebrew install, I had to `brew install automake`\. I kept the libinotify libraries in its own folder at the same level as the wine-src folder\.
-
`--with-mingw` is here because we weren't supposed to install a separate clang to build this for Mac\. I made this incorrect assumption\. The `brew install mingw` from before is enough\. But that occurred in the arm64 Homebrew install\. I did not reinstall in the x86 Homebrew install, instead I made use of original arm64 install\. Seems like Mac is not picky about mixing x86 and arm64 which is strange, considering the zsh killed error from before\.
-
On that note, I am also using the arm64 bison executable directly too
-
**IMPORTANT**: If you're following along with me, make sure to redo the following brew steps **in the x86 terminal**\.
| | |
| - | - |
| ```
1
2
3
4
5
``` | ```
# build time dependencies
brew install --formula bison mingw-w64 pkgconfig
# run time dependencies
brew install --formula freetype gnutls molten-vk sdl2
``` |
Do as I say but not as I do :\)
-
It seems that `--enable-archs=i386,x86_64` is the proper way to do WoW64\. Seems like there was a *new* WoW64 alternative introduced sometime, see this [forum post](https://forum.winehq.org/viewtopic.php?t=37610)\.
- So for the rest of this post I won't be following the original build instructions about the two-folder WoW64 build process\.
##### This leads me to a new set of environment variables:
| | |
| - | - |
| ```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
``` | ```
# define additional search path for .pc files, this points to the libinotify I built
export PKG_CONFIG_PATH="/Users/me/Documents/wine/libinotify"
# my PATH already includes /opt/homebrew/bin for mingw
# **NOTE**: if you're following along, you should use the path to the x86 homebrew, /usr/local/bin
export PATH="/opt/homebrew/opt/bison/bin:$PATH"
# x86 homebrew X11 manually built libinotify
export CPPFLAGS=" -I/usr/local/include -I/usr/X11/include $(pkg-config --cflags libinotify)"
# x86 homebrew X11 manually built libinotify
export LDFLAGS=" -L/usr/local/lib -L/usr/X11/lib $(pkg-config --libs libinotify) "
# important for Wine doing its dlopen
export DYLD_FALLBACK_LIBRARY_PATH="/usr/local/lib:/usr/lib:/usr/X11/lib:$(pkg-config --variable=libdir libinotify):${DYLD_FALLBACK_LIBRARY_PATH}"
# wine prefixes are easy ways to separate installs for Windows applications
# I've set it up just next to the build folder
export WS=$(pwd)
export WINEPREFIX="${WS}/prefix"
``` |
I may have had to brew install more things, but unfortunately I can no longer recall\. Looking at `brew leaves --installed-on-request` \(which tells you what things you ran `brew install` on\), I see that I also had to install `gstreamer` and `libpthread-stubs`\. Whenever the configure script fails, it is pretty straightforward to guess what else you will need to install\.
##### IMPORTANT NOTE\!\!
I noticed that even after installing the x86 version of different packages, **it was still running the existing arm64 one**\. It turns out that Mac stores the full-path resolution of a command name\. So after you finish all your brew installs, and right before running configure, make sure to run the following:
| | |
| - | - |
| ```
1
2
``` | ```
# for clearing command cache
hash -r
``` |
## it's built, now what?
Let's give a try\.
| | |
| - | - |
| ```
1
``` | ```
build-wine64 % ./wine ~/Downloads/SteamSetup.exe
``` |
For the first time using a new prefix, Wine will set up some config and you'll see a little window mentioning it's doing some work\. When it's done, the exe window should show up\.

Yay\!\!
When the Steam installer finishes, by default it will `Run Steam` when you click finish\.
The Steam installer itself is small because it will separately download the rest of the app via an update\.
When it finished updating, it looked like the updater finished without starting up Steam \(I waited 10 minutes\)\. So I'll start it manually\.
| | |
| - | - |
| ```
1
``` | ```
build-wine64 % ./wine "/Users/me/Documents/wine/prefix/drive_c/Program Files (x86)/Steam/steam.exe"
``` |
**But\.\.\.** it kept stopping with logs like this at the end\. Although, this *does not* look like the reason why it stopped\.
| | |
| - | - |
| ```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
``` | ```
01a4:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x35c,0x34ff0c0,0x00000010,0x0) stub
029c:fixme:winsock:setsockopt Ignoring SO_RANDOMIZE_PORT
01a4:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x68c,0x34ff0c0,0x00000010,0x0) stub
01a4:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x718,0x34ff0c0,0x00000010,0x0) stub
01a4:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x730,0x34ff0c0,0x00000010,0x0) stub
01ac:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
01ac:fixme:file:ReplaceFileW Ignoring flags 2
029c:fixme:winsock:setsockopt Ignoring SO_RANDOMIZE_PORT
01ac:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
029c:fixme:winsock:setsockopt Ignoring SO_RANDOMIZE_PORT
01a4:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
01a4:fixme:file:ReplaceFileW Ignoring flags 2
01a4:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
01a4:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
01a4:fixme:file:ReplaceFileW Ignoring flags 2
01a4:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
0254:fixme:kernelbase:AppPolicyGetProcessTerminationMethod FFFFFFFFFFFFFFFA, 000000000010FE80
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f610,0x00000010,0x0) stub
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f4e0,0x00000010,0x0) stub
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f4e0,0x00000010,0x0) stub
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f4e0,0x00000010,0x0) stub
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f550,0x00000010,0x0) stub
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f550,0x00000010,0x0) stub
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f550,0x00000010,0x0) stub
0148:fixme:process:NtQueryInformationProcess ProcessCycleTime (0x298,0x10f550,0x00000010,0x0) stub
026c:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
0258:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
01a8:fixme:thread:NtSetInformationThread ThreadPowerThrottling stub!
0148:fixme:win:UnregisterPowerSettingNotification (00000000DEADBEEF): stub
0148:fixme:win:UnregisterPowerSettingNotification (00000000DEADBEEF): stub
0148:fixme:kernelbase:AppPolicyGetProcessTerminationMethod FFFFFFFFFFFFFFFA, 000000000010FE80
016c:fixme:kernelbase:AppPolicyGetProcessTerminationMethod FFFFFFFFFFFFFFFA, 000000000010FE80
``` |
I had to run the Steam\.exe manually \(i\.e\. the command as above\) a few times before I finally got a window\.
But it was a black window\.\.\.
You can tell that most of the window is \"there\", since you can hover and see the mouse icon change from a pointer to a text-input cursor when you hover over where the username & password fields should be\. Also, you can click the top right corner and the window will close\.
Since Steam is widely used with Wine, I searched for info about the black login screen\.
I was told that adding the flags `--no-sandbox --in-process-gpu --disable-gpu` would help, but I still saw the same black screen behaviour\.
Side note, sometimes I want to make sure everything is stopped, so I use the following commands to stop Wine\.
| | |
| - | - |
| ```
1
2
3
4
5
``` | ```
# kill all wine processes
./server/wineserver -k
# kill all steam processes in case they weren't killed (happened once)
kill -9 `pgrep -f -d ' ' steam`
``` |
I ended up sifting through the logs since I was out of ideas, and found this very interesting line:
| | |
| - | - |
| ```
1
2
``` | ```
0438:fixme:d3d:wined3d_context_gl_init OpenGL implementation does not support GL_PRIMITIVE_RESTART_FIXED_INDEX.
0438:err:d3d:wined3d_check_gl_call >>>>>>> GL_INVALID_FRAMEBUFFER_OPERATION (0x506) from glClear @ ../wine-src/dlls/wined3d/context_gl.c / 2298.
``` |
Excuse me? I thought we wanted Vulkan\.\.\. If there's something wrong with OpenGL that would fully explain the black screen\. Mac deprecated OpenGL in Mojave\.
So it was at this point I decided to look for proof that the Vulkan/MoltenVK part of Wine is working\.
## hunt for Vulkan
If you run the wine-stable \(pre-built version of Wine\), the logs look quite different for Steam\.
The biggest difference are MoltenVK logs,
| | |
| - | - |
| ```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
``` | ```
[mvk-info] MoltenVK version 1.4.0, supporting Vulkan version 1.4.323.
The following 145 Vulkan extensions are supported:
VK_KHR_16bit_storage v1
VK_KHR_8bit_storage v1
VK_KHR_bind_memory2 v1
VK_KHR_buffer_device_address v1
VK_KHR_calibrated_timestamps v1
...
[mvk-info] GPU device:
model: Apple M2
type: Integrated
vendorID: 0x106b
deviceID: 0xf050208
pipelineCacheUUID: 000028A0-0F05-0208-0000-000100000000
GPU memory available: 5461 MB
GPU memory used: 0 MB
Metal Shading Language 3.2
supports the following GPU Features:
GPU Family Metal 3
GPU Family Apple 8
GPU Family Mac 2
Read-Write Texture Tier 2
[mvk-info] Created VkInstance for Vulkan version 1.0.323, as requested by app, with the following 2 Vulkan extensions enabled:
VK_KHR_external_memory_capabilities v1
VK_KHR_get_physical_device_properties2 v2
``` |
This made me think that MoltenVK wasn't working properly in my build since I didn't see these logs\. But as far as compiling and linking went, there were no indications something went wrong\. So I suspected Wine wasn't hooking into MoltenVK properly\.
After I did a bit of searching related to Wine, I discovered that these logs were from MoltenVK, *not* from Wine \(fairly obvious in hindsight, it literally says \"mvk-info\"\)\.
I learned I could get the same logs with:
| | |
| - | - |
| ```
1
``` | ```
export MVK_CONFIG_LOG_LEVEL=4
``` |
Now I have the same MoltenVK logs as wine-stable, except I still see the same OpenGL error\.
So MoltenVK is working, but Wine isn't trying to use Vulkan\.
I started my search in the dlls/wined3d code \(due to where the error log came from\), and found the following interesting snippet from the file `wined3d_main.c` on line 134:
| | |
| - | - |
| ```
1
2
3
4
5
6
7
``` | ```
enum wined3d_renderer CDECL wined3d_get_renderer(void)
{
if (wined3d_settings.renderer == WINED3D_RENDERER_AUTO)
return WINED3D_RENDERER_OPENGL;
return wined3d_settings.renderer;
}
``` |
\.\.\.which means the renderer is controlled by config\. Tried searching for how to change the setting and found these snippets from the same file:
| | |
| - | - |
| ```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
``` | ```
/* Allow modifying settings using the WINE_D3D_CONFIG environment variable,
* which takes precedence over registry keys. An example is as follows:
*
* WINE_D3D_CONFIG=csmt=0x1,shader_backend=glsl
*/
env = getenv("WINE_D3D_CONFIG");
...
if (!get_config_key(hkey, appkey, env, "renderer", buffer, size))
{
if (!strcmp(buffer, "vulkan"))
{
ERR_(winediag)("Using the Vulkan renderer.\n");
wined3d_settings.renderer = WINED3D_RENDERER_VULKAN;
}
else if (!strcmp(buffer, "gl"))
{
ERR_(winediag)("Using the OpenGL renderer.\n");
wined3d_settings.renderer = WINED3D_RENDERER_OPENGL;
}
else if (!strcmp(buffer, "gdi") || !strcmp(buffer, "no3d"))
{
ERR_(winediag)("Disabling 3D support.\n");
wined3d_settings.renderer = WINED3D_RENDERER_NO3D;
}
}
``` |
So with this, I try the following:
| | |
| - | - |
| ```
1
``` | ```
build-wine64 % WINE_D3D_CONFIG=renderer=vulkan ./wine "/Users/me/Documents/wine/prefix/drive_c/Program Files (x86)/Steam/steam.exe"
``` |
And\.\.\.
| | |
| - | - |
| ```
1
``` | ```
0208:err:d3d:wined3d_swapchain_vk_create_vulkan_swapchain Failed to create Vulkan surface, vr VK_ERROR_OUT_OF_HOST_MEMORY.
``` |
Okay\.\.\.
I found an interesting [thread on GitHub](https://github.com/KhronosGroup/MoltenVK/issues/1650) about this\. It is an issue opened against MoltenVK that is still open at the time of writing this post\.
Basically, when the issue was reported, there was a change pushed to MoltenVK to fix the error, the cause is that by-default Metal pre-allocates huge amounts of video memory for the swapchain\. But the change was quickly reverted due to breaking other things and ultimately it was decided that patches should be made at the app level, not MoltenVK\. Someone who works on Crossover [commented that they implemented a workaround](https://github.com/KhronosGroup/MoltenVK/issues/1650#issuecomment-1204069666), which explains why Crossover works fine with Steam\.
So what I've learned is that Crossover code is not just a copy of Wine\!
## next steps
Given the known workaround, I will try and patch Wine myself and see how it goes\. Stay tuned for the next part\. Thanks for reading\!