We need to figure out a way to adapt the features of the amethyst dependency to the platform the game is being compiled on

(Erlend Sogge Heggen) #1

Original issue.

We need to figure out a way to adapt the features of the amethyst dependency to the platform the game is being compiled on.

Right now, it won’t compile on MacOS as it needs the “metal” feature while the other platforms need “vulkan”.
Since Jenkins tests on Linux (and Windows when the machine is online), I put the “vulkan” feature back in.

1 Like
(Zicklag) #3

This has been handled cleanly handled in wgpu-rs and we could learn a lot from that. It used to have the same problem, but it was adapted to detect the required backend at runtime.

#4

I was looking into this just yesterday. I noticed that Grumpy Visitors has an interesting conditional-dependency construction in its cargo files. It is documented here.

Extract:

[target.'cfg(target_os = "macos")'.dependencies.amethyst]
version = "0.15"
features = ["metal"]

[target.'cfg(not(target_os = "macos"))'.dependencies.amethyst]
version = "0.15"
features = ["vulkan"]

However, I wasn’t able to get this work myself. I don’t know if you have to do something special to get this to work. If so, it kinda defeats the purpose of making thing easier on users.

(Zicklag) #5

I’ll have to double-check, but I think I tried that and it doesn’t actually work. There is like a nightly cargo feature that makes it work or something:

You can’t override feature selection based on the target without that nightly feature.

1 Like
(Zicklag) #6

Oh, OK, I’m starting to remember how I handled this with gfx-backend-gl here.

So you have to structure your code so that you can enable any feature on any platform and not cause a compilation issue. If the feature is not supported on a particular platform ( such as metal on Windows ) then you need to make sure you gate all of your metal code by using a cfg check that checks not only the feature but also the platform. For example instead of doing #[cfg(feature = "metal")] you would need to do #[cfg(feature = "metal", os = "macos")]. That way you can compile Amethyst with the metal, vulkan, and directx12 features all enabled at the same time, and it would use the correct one based on your OS.

Adding all those extra parameters to your cfg checks can get very messy and hard to maintain, so I created the cfg_aliases crate so that you can just do #[cfg(use_metal)] and it can resolve to an arbitrarily complex cfg expression that you set in your build.rs file. This allows you to keep your code’s cfg checks clean and easy to understand and maintain.


The purpose of cargo features, then, is not to make the user select which features are needed for their platform, but to allow them to opt-in or opt-out of features that apply to any platform. For example, say you had the following features ( hypothetically ):

  • gl ( works on all platforms )
  • metal ( works on MacOS )
  • vulcan ( works on Linux )
  • directx12 ( works on Windows )

All of these features would be enabled by default, and it would not break on any platform. If built on Windows, the vulcan and metal features, which would be cfg gated with additional checks for the target platform, would effectively be ignored.

If you didn’t want gl support, you could disable it. Or if you only wanted to use gl on Linux, you could disable vulcan. If you disabled both gl and vulcan and you built on a Linux machine, it would throw a compiler error.

If you wanted, you could also make platform specific features to allow you to enable or disable gl only on certain platforms: gl-windows, gl-mac, gl-linux.

All of this is made simple to check for with cfg_aliases that let you define things like so:

build.rs:

use cfg_aliases::cfg_aliases;

fn main() {
    // Setup cfg aliases
    cfg_aliases! {
        // Platforms
        wasm: { target_arch = "wasm32" },
        windows: { target_os = "windows", not(wasm) },
        macos: { target_os = "macos", not(wasm) },
        linux: { target_os = "linux", not(wasm) },
        // Backends
        gl: { all(any(windows, macos, linux, wasm), feature = "gl") },
        vulkan: { all(linux, feature = "vulkan") },
        directx12: { all(windows, feature = "directx12") },
        metal: { all(macos, feature = "metal") },
        dummy: { not(any(wasm, linux, windows, macos)) },
    }
}

Then in your code you can do stuff like:

#[cfg(metal)]
fn init_graphics() {
    // Init metal graphics
}

#[cfg(vulkan)]
fn init_graphics() {
   // Init vulcan graphics
}

#[cfg(all(gl, not(any(vulkan, directx12, metal)))]
fn init_graphics() {
  // Init GL graphics
}

// And so on...

#[cfg(dummy)]
compile_warning!("Compiling without graphics");

Those examples are all a little contrived, but hopefully that gives the idea.

3 Likes
#7

Amazing! This would solve so much friction!

1 Like
#8

As the issue zicklag linked to mentions, the feature resolver on current stable rust sometimes have issues when messing with target or dev specific features.
Hopefully gets better once the new feature resolver gets stabilized.

1 Like