1
0
Fork 0
mirror of https://github.com/nix-community/home-manager synced 2024-11-01 08:49:44 +01:00

nixgl: add module

This commit is contained in:
Robert Helgesson 2024-10-25 10:22:57 +02:00
commit 5feb9dba3c
No known key found for this signature in database
GPG key ID: 96E745BD17AA17ED
5 changed files with 391 additions and 0 deletions

View file

@ -59,5 +59,6 @@ usage/configuration.md
usage/rollbacks.md usage/rollbacks.md
usage/dotfiles.md usage/dotfiles.md
usage/graphical.md usage/graphical.md
usage/gpu-non-nixos.md
usage/updating.md usage/updating.md
``` ```

View file

@ -0,0 +1,81 @@
# GPU on non-NixOS systems {#sec-usage-gpu-non-nixos}
To access the GPU, programs need access to OpenGL and Vulkan libraries. While
this works transparently on NixOS, it does not on other Linux systems. A
solution is provided by [NixGL](https://github.com/nix-community/nixGL), which
can be integrated into Home Manager.
To enable the integration, import NixGL into your home configuration, either as
a channel, or as a flake input passed via `extraSpecialArgs`. Then, set the
`nixGL.packages` option to the package set provided by NixGL.
Once integration is enabled, it can be used in two ways: as Nix functions for
wrapping programs installed via Home Manager, and as shell commands for running
programs installed by other means (such as `nix shell`). In either case, there
are several wrappers available. They can be broadly categorized
- by vendor: as Mesa (for Free drivers of all vendors) and Nvidia (for
Nvidia-specific proprietary drivers).
- by GPU selection: as primary and secondary (offloading).
For example, the `mesa` wrapper provides support for running programs on the
primary GPU for Intel, AMD and Nouveau drivers, while the `mesaPrime` wrapper
does the same for the secondary GPU.
**Note:** when using Nvidia wrappers together with flakes, your home
configuration will not be pure and needs to be built using `home-manager switch
--impure`. Otherwise, the build will fail, complaining about missing attribute
`currentTime`.
Wrapper functions are available under `config.lib.nixGL.wrappers`. However, it
can be more convenient to use the `config.lib.nixGL.wrap` alias, which can be
configured to use any of the wrappers. It is intended to provide a customization
point when the same home configuration is used across several machines with
different hardware. There is also the `config.lib.nixGL.wrapOffload` alias for
two-GPU systems.
Another convenience is that all wrapper functions are always available. However,
when `nixGL.packages` option is unset, they are no-ops. This allows them to be
used even when the home configuration is used on NixOS machines. The exception
is the `prime-offload` script which ignores `nixGL.packages` and is installed
into the environment whenever `nixGL.prime.installScript` is set. This script,
which can be used to start a program on a secondary GPU, does not depend on
NixGL and is useful on NixOS systems as well.
Below is an abbreviated example for an Optimus laptop that makes use of both
Mesa and Nvidia wrappers, where the latter is used in dGPU offloading mode. It
demonstrates how to wrap `mpv` to run on the integrated Intel GPU, wrap FreeCAD
to run on the Nvidia dGPU, and how to install the wrapper scripts. It also wraps
Xonotic to run on the dGPU, but uses the wrapper function directly for
demonstration purposes.
```nix
{ config, lib, pkgs, nixgl, ... }:
{
nixGL.packages = nixgl.packages;
nixGL.defaultWrapper = "mesa";
nixGL.offloadWrapper = "nvidiaPrime";
nixGL.installScripts = [ "mesa" "nvidiaPrime" ];
programs.mpv = {
enable = true;
package = config.lib.nixGL.wrap pkgs.mpv;
};
home.packages = [
(config.lib.nixGL.wrapOffload pkgs.freecad)
(config.lib.nixGL.wrappers.nvidiaPrime pkgs.xonotic)
];
}
```
The above example assumes a flake-based setup where `nixgl` was passed from the
flake. When using channels, the example would instead begin with
```nix
{ config, lib, pkgs, ... }:
{
nixGL.packages = import <nixgl> { inherit pkgs; };
# The rest is the same as above
...
```

View file

@ -1801,6 +1801,18 @@ in {
itself. itself.
''; '';
} }
{
time = "2024-10-25T08:18:30+00:00";
condition = hostPlatform.isLinux;
message = ''
A new module is available: 'nixGL'.
NixGL solve the "OpenGL" problem with nix. The 'nixGL' module provides
integration of NixGL into Home Manager. See the "GPU on non-NixOS
systems" section in the Home Manager mantual for more.
'';
}
]; ];
}; };
} }

296
modules/misc/nixgl.nix Normal file
View file

@ -0,0 +1,296 @@
{ config, lib, pkgs, ... }:
let
cfg = config.nixGL;
wrapperListMarkdown = with builtins;
foldl' (list: name:
list + ''
- ${name}
'') "" (attrNames config.lib.nixGL.wrappers);
in {
meta.maintainers = [ lib.maintainers.smona ];
options.nixGL = {
packages = lib.mkOption {
type = with lib.types; nullOr attrs;
default = null;
example = lib.literalExpression "inputs.nixGL.packages";
description = ''
The nixGL package set containing GPU library wrappers. This can be used
to provide OpenGL and Vulkan access to applications on non-NixOS systems
by using `(config.lib.nixGL.wrap <package>)` for the default wrapper, or
`(config.lib.nixGL.wrappers.<wrapper> <package>)` for any available
wrapper.
The wrapper functions are always available. If this option is empty (the
default), they are a no-op. This is useful on NixOS where the wrappers
are unnecessary.
Note that using any Nvidia wrapper requires building the configuration
with the `--impure` option.
'';
};
defaultWrapper = lib.mkOption {
type = lib.types.enum (builtins.attrNames config.lib.nixGL.wrappers);
default = "mesa";
description = ''
The package wrapper function available for use as `(config.lib.nixGL.wrap
<package>)`. Intended to start programs on the main GPU.
Wrapper functions can be found under `config.lib.nixGL.wrappers`. They
can be used directly, however, setting this option provides a convenient
shorthand.
The following wrappers are available:
${wrapperListMarkdown}
'';
};
offloadWrapper = lib.mkOption {
type = lib.types.enum (builtins.attrNames config.lib.nixGL.wrappers);
default = "mesaPrime";
description = ''
The package wrapper function available for use as
`(config.lib.nixGL.wrapOffload <package>)`. Intended to start programs
on the secondary GPU.
Wrapper functions can be found under `config.lib.nixGL.wrappers`. They
can be used directly, however, setting this option provides a convenient
shorthand.
The following wrappers are available:
${wrapperListMarkdown}
'';
};
prime.card = lib.mkOption {
type = lib.types.str;
default = "1";
example = "pci-0000_06_00_0";
description = ''
Selects the non-default graphics card used for PRIME render offloading.
The value can be:
- a number, selecting the n-th non-default GPU;
- a PCI bus id in the form `pci-XXX_YY_ZZ_U`;
- a PCI id in the form `vendor_id:device_id`
For more information, consult the Mesa documentation on the `DRI_PRIME`
environment variable.
'';
};
prime.nvidiaProvider = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
example = "NVIDIA-G0";
description = ''
If this option is set, it overrides the offload provider for Nvidia
PRIME offloading. Consult the proprietary Nvidia driver documentation
on the `__NV_PRIME_RENDER_OFFLOAD_PROVIDER` environment variable.
'';
};
prime.installScript = lib.mkOption {
type = with lib.types; nullOr (enum [ "mesa" "nvidia" ]);
default = null;
example = "mesa";
description = ''
If this option is set, the wrapper script `prime-offload` is installed
into the environment. It allows starting programs on the secondary GPU
selected by the `nixGL.prime.card` option. This makes sense when the
program is not already using one of nixGL PRIME wrappers, or for
programs not installed from Nixpkgs.
This option can be set to either "mesa" or "nvidia", making the script
use one or the other graphics library.
'';
};
installScripts = lib.mkOption {
type = with lib.types;
nullOr (listOf (enum (builtins.attrNames config.lib.nixGL.wrappers)));
default = null;
example = [ "mesa" "mesaPrime" ];
description = ''
For each wrapper `wrp` named in the provided list, a wrapper script
named `nixGLWrp` is installed into the environment. These scripts are
useful for running programs not installed via Home Manager.
The following wrappers are available:
${wrapperListMarkdown}
'';
};
vulkan.enable = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = ''
Whether to enable Vulkan in nixGL wrappers.
This is disabled by default bacause Vulkan brings in several libraries
that can cause symbol version conflicts in wrapped programs. Your
mileage may vary.
'';
};
};
config = let
findWrapperPackage = packageAttr:
# NixGL has wrapper packages in different places depending on how you
# access it. We want HM configuration to be the same, regardless of how
# NixGL is imported.
#
# First, let's see if we have a flake.
if builtins.hasAttr pkgs.system cfg.packages then
cfg.packages.${pkgs.system}.${packageAttr}
else
# Next, let's see if we have a channel.
if builtins.hasAttr packageAttr cfg.packages then
cfg.packages.${packageAttr}
else
# Lastly, with channels, some wrappers are grouped under "auto".
if builtins.hasAttr "auto" cfg.packages then
cfg.packages.auto.${packageAttr}
else
throw "Incompatible NixGL package layout";
getWrapperExe = vendor:
let
glPackage = findWrapperPackage "nixGL${vendor}";
glExe = lib.getExe glPackage;
vulkanPackage = findWrapperPackage "nixVulkan${vendor}";
vulkanExe = if cfg.vulkan.enable then lib.getExe vulkanPackage else "";
in "${glExe} ${vulkanExe}";
mesaOffloadEnv = { "DRI_PRIME" = "${cfg.prime.card}"; };
nvOffloadEnv = {
"DRI_PRIME" = "${cfg.prime.card}";
"__NV_PRIME_RENDER_OFFLOAD" = "1";
"__GLX_VENDOR_LIBRARY_NAME" = "nvidia";
"__VK_LAYER_NV_optimus" = "NVIDIA_only";
} // (let provider = cfg.prime.nvidiaProvider;
in if !isNull provider then {
"__NV_PRIME_RENDER_OFFLOAD_PROVIDER" = "${provider}";
} else
{ });
makePackageWrapper = vendor: environment: pkg:
if builtins.isNull cfg.packages then
pkg
else
# Wrap the package's binaries with nixGL, while preserving the rest of
# the outputs and derivation attributes.
(pkg.overrideAttrs (old: {
name = "nixGL-${pkg.name}";
# Make sure this is false for the wrapper derivation, so nix doesn't expect
# a new debug output to be produced. We won't be producing any debug info
# for the original package.
separateDebugInfo = false;
nativeBuildInputs = old.nativeBuildInputs or [ ]
++ [ pkgs.makeWrapper ];
buildCommand = let
# We need an intermediate wrapper package because makeWrapper
# requires a single executable as the wrapper.
combinedWrapperPkg =
pkgs.writeShellScriptBin "nixGLCombinedWrapper-${vendor}" ''
exec ${getWrapperExe vendor} "$@"
'';
in ''
set -eo pipefail
${ # Heavily inspired by https://stackoverflow.com/a/68523368/6259505
lib.concatStringsSep "\n" (map (outputName: ''
echo "Copying output ${outputName}"
set -x
cp -rs --no-preserve=mode "${
pkg.${outputName}
}" "''$${outputName}"
set +x
'') (old.outputs or [ "out" ]))}
rm -rf $out/bin/*
shopt -s nullglob # Prevent loop from running if no files
for file in ${pkg.out}/bin/*; do
local prog="$(basename "$file")"
makeWrapper \
"${lib.getExe combinedWrapperPkg}" \
"$out/bin/$prog" \
--argv0 "$prog" \
--add-flags "$file" \
${
lib.concatStringsSep " " (lib.attrsets.mapAttrsToList
(var: val: "--set '${var}' '${val}'") environment)
}
done
# If .desktop files refer to the old package, replace the references
for dsk in "$out/share/applications"/*.desktop ; do
if ! grep -q "${pkg.out}" "$dsk"; then
continue
fi
src="$(readlink "$dsk")"
rm "$dsk"
sed "s|${pkg.out}|$out|g" "$src" > "$dsk"
done
shopt -u nullglob # Revert nullglob back to its normal default state
'';
}));
wrappers = {
mesa = makePackageWrapper "Intel" { };
mesaPrime = makePackageWrapper "Intel" mesaOffloadEnv;
nvidia = makePackageWrapper "Nvidia" { };
nvidiaPrime = makePackageWrapper "Nvidia" nvOffloadEnv;
};
in {
lib.nixGL.wrap = wrappers.${cfg.defaultWrapper};
lib.nixGL.wrapOffload = wrappers.${cfg.offloadWrapper};
lib.nixGL.wrappers = wrappers;
home.packages = let
wantsPrimeWrapper = (!isNull cfg.prime.installScript);
wantsWrapper = wrapper:
(!isNull cfg.packages) && (!isNull cfg.installScripts)
&& (builtins.elem wrapper cfg.installScripts);
envVarsAsScript = environment:
lib.concatStringsSep "\n"
(lib.attrsets.mapAttrsToList (var: val: "export ${var}=${val}")
environment);
in [
(lib.mkIf wantsPrimeWrapper (pkgs.writeShellScriptBin "prime-offload" ''
${if cfg.prime.installScript == "mesa" then
(envVarsAsScript mesaOffloadEnv)
else
(envVarsAsScript nvOffloadEnv)}
exec "$@"
''))
(lib.mkIf (wantsWrapper "mesa") (pkgs.writeShellScriptBin "nixGLMesa" ''
exec ${getWrapperExe "Intel"} "$@"
''))
(lib.mkIf (wantsWrapper "mesaPrime")
(pkgs.writeShellScriptBin "nixGLMesaPrime" ''
${envVarsAsScript mesaOffloadEnv}
exec ${getWrapperExe "Intel"} "$@"
''))
(lib.mkIf (wantsWrapper "nvidia")
(pkgs.writeShellScriptBin "nixGLNvidia" ''
exec ${getWrapperExe "Nvidia"} "$@"
''))
(lib.mkIf (wantsWrapper "nvidia")
(pkgs.writeShellScriptBin "nixGLNvidiaPrime" ''
${envVarsAsScript nvOffloadEnv}
exec ${getWrapperExe "Nvidia"} "$@"
''))
];
};
}

View file

@ -31,6 +31,7 @@ let
./misc/gtk.nix ./misc/gtk.nix
./misc/lib.nix ./misc/lib.nix
./misc/news.nix ./misc/news.nix
./misc/nixgl.nix
./misc/numlock.nix ./misc/numlock.nix
./misc/pam.nix ./misc/pam.nix
./misc/qt.nix ./misc/qt.nix