Building Outer Wonders for Windows

Building Outer Wonders for Windows

#outer-wonders #technology

Hi! Many of you have shared our blog post about building Outer Wonders for Linux, sparking impassioned debates and discussions, where the Linux game development experience was very often compared to the Windows game development experience. This is why today's blog post will discuss how we built the demo of Outer Wonders for Windows!

Despite our effort in simplifying this blog post, reading it will require a bit of technical knowledge, similarly to our blog post about building Outer Wonders for Linux.

Fragmentation and backwards compatibility

Fragmentation built over decades

Just like Linux, Windows cannot really be considered as a single platform. Because there are so many versions of Windows, one specific parameter must be considered: backwards compatiblity. To put it simply: will a game built on Windows 10 hardware work on Windows 8 hardware? What about Windows 7 hardware? How far back in the timeline of Windows versions can the developer go while keeping their games functional?

Before considering the use of a specific Windows feature, the developer has to check whether the feature is supported by the minimal Windows version that they want their game to support.

For instance, traditional applications (those downloaded outside the Windows Store), also referred to as Win32 applications, are all based on the concept of opening windows containing interactive elements. Opening windows is made possible thanks to a set of features named CreateWindowA(), CreateWindowW(), CreateWindowExA(), and CreateWindowExW(). These features have been part of Windows for over 20 years (some of them even date from over 30 years ago).

This is where the truth hits hard: a basic concept (such as opening windows) is implemented in multiple distinct features (4 here), thereby making application development rather complex. And the concept of opening windows is not the only example of this: retrieving information about the user's Windows version is made possible by 3 different features named GetVersion(), GetVersionExA(), and GetVersionExW().

This fragmentation is due to historical reasons: some features exist in 2 different versions, the first one having its name end with an A, and the second one having its name end with a W. The first one only accepts ANSI text (in other words: only latin characters), while the second one accepts UTF-16 text (in other words: it supports significantly more characters, making the use of chinese or arabic characters possible, for example).

Decades ago, computer technology was not mature enough to allow the use of non-latin characters, and the only features that existed back then were equivalent to the now A-ending ones. Turning computer technology into an international business created a need for better support of new characters, causing Windows designers to add the W-ending counterpart of each of these features, which would be more flexible than their A counterpart. After all, it was logical for e.g. japanese users to be able to open windows with a title written in japanese characters!

What we could wonder now is: why did the Windows designers add a new version of each feature besides the existing ones, rather than just replacing or improving the existing ones? The answer is just two words: backwards compatibility. Microsoft has an user-centric policy about this: all or most users need to be able to enjoy their applications, even the old ones, and even on the latest cutting-edge Windows build. Which is why old features, despite their obvious limitations, must be kept as-is. This is why a bunch of games created in the 2000s are still playable on recent Windows builds.

While this is convenient for users, the developer experience is another story, as developers then have to decide whether they will make use of modern and more complete features while losing users who don't have an up-to-date system, or make use of more limited features and keep users who don't need or don't want to update their system.

Fortunately enough, tools have been designed since then to allow developers to worry significantly less about making such decisions.

Using SDL2 to access features in a simplified way

We mentioned why abstraction layers such as SDL2 are great already in our blog post about building Outer Wonders for multiple platforms and in our blog post about building Outer Wonders for Linux, and today's blog post will be yet another example of why they really should be considered when building Windows games.

As mentioned at the beginning of this blog post, Windows cannot really be considered as a single platform, but rather multiple platforms. In this context, SDL2 plays its role perfectly again, as it can automatically determine what features are present on the user's hardware, and make use of the most recent features. Thanks to considerable work carried out continuously by its developers over several decades and many generations of Windows, SDL2 makes it possible for game developers to support various Windows versions as a single, unified platform.

This is why we would strongly recommend that developers making a game using an in-house engine make use of SDL2 or a similar tool instead of directly using Windows features, even if they are targeting Windows only.

In some rare cases, it takes more to achieve great Windows support

A few platform-specific features are not fully supported by abstraction layers such as SDL2 yet.

User language detection is one of them. Even if initial support for this feature was added as part of SDL 2.0.14, it is not yet listed in the SDL developer documentation.

At first glance, retrieving the user's languages should be a simple task. All the system has to do is read the list of languages displayed by the following interface that can be accessed from the Windows settings:

Windows settings interface displaying the user's preferred languages. The user has set US english as their main language, and 3 secondary languages listed in the following order: japanese, France's french, as well as korean.

A quick search in the Windows documentation will take developers to a feature named GetUserPreferredUILanguages(), which provides the user's preferred languages to a Windows application.

We were surprised with the results we got when we used this feature, though. In the situation where the user has defined the languages displayed in the screenshot above as their preferred languages, this feature yielded just US english and no other language. Japanese, France's french and korean were simply ignored. The experiments we carried out afterwards led us to the conclusion that this feature added as part of Windows Vista only yields the user's main language (if not US english), followed by US english.

We had to perform a long search to finally figure out the existence of a much less-known feature implemented added as part of Windows 8, named (hold your breath!) Windows.System.UserProfile.IGlobalizationPreferencesStatics::Languages(). Upon testing with the same example shown in the screenshot above, this feature correctly yielded the user's preferred languages in the correct order: US english (en-US), japanese (ja), France's french (fr-FR) and korean (ko).

As far as we know, few tools and aplications make use of this recent feature. For instance, SDL2 does not, but maybe support for this feature could be added as part of an update to SDL2.

The complex situation of DirectX…

…or, rather, Direct3D, which is the 3D rendering technology provided as part of Windows.

Why Outer Wonders defaults to Direct3D 11 for rendering

Why mention Direct3D? Actually, similar competing technologies, such as Vulkan and OpenGL (as well as its variant named OpenGL ES) exist, and on top of that, they are compatible with more platforms, such as Linux (as well as Android, which is based on Linux).

However, in our expérience, OpenGL support quality on Windows varies wildly. On several of our machines, using OpenGL 3.3 causes 100% usage of a full CPU core (which is rather unexpected and energy-consuming) and yields performance which, while correct, is approximately 40% lower compared to what the results we obtained by using Vulkan 1.0 and Direct3D 11.

As for Vulkan, even if the wide majority of consumer hardware supports it nowadays, there are still a non-negligible amount of players who don't own Vulkan-capable hardware. As of writing, over 99% of Direct3D 12-capable hardware is also Vulkan-capable, but approximately 8% of Windows players don't have Direct3D 12-capable (and therefore Vulkan-capable) hardware, as reported by the Steam hardware and software survey, and approximately 42% of Android users don't have a Vulkan-capable phone or tablet.

It can be tempting for developers to decide to make use of the latest technology, such as Vulkan or Direct3D 12, despite these figures. However, both of these technologies are fairly complex, and require significantly more work from developers than what older technologies such Direct3D 9 to 11 and OpenGL required. Besides, Vulkan testing on our end made us experience some pretty nasty bugs in some Vulkan implementations.

Given all of this, we made the decision to default to Direct3D 11 rendering for all Windows players (although alternative rendering backends based on OpenGL 3.3 and Vulkan 1.0 are available as well) playing Outer Wonders.

Achieving maximal compatiblity with older Windows versions

Another argument in favor of Direct3D 11 the compatibility of Direct3D 11 with many distinct versions of Windows, ranging from Windows 10 to Windows… Vista. Which is rarely the case of Vulkan and OpenGL, of which supported versions actually depend on the player's video card driver.

Picking Direct3D 11 does not necessarily guarantee that the developer's game will support older Windows builds, though. One additional detail must be considered: the feature level supported by the user's hardware.

To make use of Direct3D 11, developers must use one of the features named D3D11CreateDevice and D3D11CreateDeviceAndSwapChain. Both of these features will default to fully making use of Direct3D 11 features, or fall back to Direct3D 10 features or Direct3D 9 features should the user's video card fail to support Direct3D 11.

Diagram showing the connection between a game and Direct3D 11. The game uses Direct3D 11 interface which, itself, uses either the Direct3D 11 features, or those of Direct3D 10, of possibly those of Direct3D 9.

Because of this, when a game uses Direct3D 11 for rendering, the features actually used under the hood might be those of Direct3D 10 or Direct3D 9, even if all of this is hidden the Direct3D 11 interface. This is what Microsoft calls Direct3D feature levels. Therefore, when a game mandates "DirectX 10 support" as part of its minimum or recommended specifications, this probably means the game could be based on Direct3D 11, but only requires that the feature level supported by the player's system is the Direct3D 10 feature level.

In order to achieve maximal compatibility, the developer must ensure that the Direct3D feature level required by their game is as low as possible (the lowest possible feature level is level 9.1, which can run on Direct3D 9.0a-capable hardware).

We learned all of this through a great deal of testing. On a very old Windows Vista-powered computer dating from 2007, we faced some rather unexpected errors. Upon retrieving information about the computer's video card, one detail caught our attention: the feature level supported by this vodeo card was level 9.3 (the feature level of Direct3D 9.0c-capable hardware). The way we were using Direct3D 11 made Outer Wonders incompatible with such hardware, but the fixes required here were very simple and boiled down to changing a few parameters in the way we used Direct3D 11 features.

One interesting detail to know, though, is that developers don't necessarily need old computers to detect and fix Direct3D compatibility errors. Windows allows developers to emulate any Direct3D feature level lower or equal to the feature level supported by the developer's hardware. Features D3D11CreateDevice and D3D11CreateDeviceAndSwapChain allow developers to set the Direct3D feature level required by the game. All the developer has to do is set this parameter to a low feature level, so that errors which could only be reproduced on older hardware so far can be reproduced on their recent hardware, making these errors easier to fix!

Stringent testing again

Just like Linux, nothing beats stringent testing on a wide range of hardware to ensure that a game will work on multiple Windows versions.

Again, the goal is to make sure that the application actually starts on all of these machines.

Errors such as the following:

"The program can't start because msvcr140.dll is missing from your computer. Try reinstalling the program to fix this problem."

may pop up on some Windows systems.

This is due to the game being built using the tool called Visual C++ (files msvcrXXX.dll and msvcpXXX.dll are actually connected to Visual C++ and must installed alongside any application built using Visual C++ – they are basically the Windows counter part of the glibc system dependency found on Linux systems, which we mentioned here). To solve this, the developer can either:

  • provide a Visual C++ redistributable file matching the Visual C++ version used for building along with their game; or
  • build their game using another tool, such as GCC or Clang, to make their game independent of Visual C++ files (i.e. files msvcrXXX.dll and msvcpXXX.dll).

We went for the second solution (in more details: because Outer Wonders is based on Rust, we used the targets named x86_64-pc-windows-gnu and i686-pc-windows-gnu) in order to spare players the additional step of installing Visual C++ dependencies, and make Outer Wonders fully portable!

Check out the screenshot below (taken from this blog post), showing Outer Wonders working on Windows Vista.

Screenshot of a Windows Vista Service Pack 2 system running Outer Wonders.

And this concludes this technical blog post! If you haven't tried it out yet, consider giving the demo of Outer Wonders a try! Join our Discord server, follow us on Twitter, Facebook and Instagram to read our news and play weekly puzzles! Subscribe to our RSS feed to keep informed about our latest blog posts.

See you soon!