diff --git a/README.md b/README.md index cdb422f5a4..960feee647 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,9 @@ the Wine prefix. Removing the option will revert to the previous behavior. | `noopwr` | `WINE_DISABLE_VULKAN_OPWR` | Enable hack to disable Vulkan other process window rendering which sometimes causes issues on Wayland due to blit being one frame behind. | | `hidenvgpu` | `PROTON_HIDE_NVIDIA_GPU` | Force Nvidia GPUs to always be reported as AMD GPUs. Some games require this if they depend on Windows-only Nvidia driver functionality. See also DXVK's nvapiHack config, which only affects reporting from Direct3D. | | | `WINE_FULLSCREEN_INTEGER_SCALING` | Enable integer scaling mode, to give sharp pixels when upscaling. | +| | `PROTON_ENABLE_IO_PRIORITY` | Enable IO and CPU priority optimizations to improve game responsiveness during heavy IO activity like recording. Set to `0` to disable. Default: `1` (enabled). | +| | `PROTON_IO_PRIORITY` | Set IO scheduling priority for games (0-7, lower = higher priority). Default: `2` (high priority). Only used when `PROTON_ENABLE_IO_PRIORITY=1`. | +| | `PROTON_NICE_VALUE` | Set CPU scheduling priority (nice value) for games (-20 to 19, lower = higher priority). Default: `-2` (slightly higher priority). Requires proper system configuration. | | `cmdlineappend:` | | Append the string after the colon as an argument to the game command. May be specified more than once. Escape commas and backslashes with a backslash. | | `xalia` | `PROTON_USE_XALIA` | Enable Xalia, a program that can add a gamepad UI for some keyboard/mouse interfaces. | | `seccomp` | `PROTON_USE_SECCOMP` | **Note: Obsoleted in Proton 5.13.** In older versions, enable seccomp-bpf filter to emulate native syscalls, required for some DRM protections to work. | diff --git a/docs/THREAD_PRIORITY.md b/docs/THREAD_PRIORITY.md index 1f340651d0..f8c619eec1 100644 --- a/docs/THREAD_PRIORITY.md +++ b/docs/THREAD_PRIORITY.md @@ -1,10 +1,14 @@ -# Thread priorities +# Thread priorities and IO scheduling Proton supports fine-grained thread priority control using `setpriority(2)` to set thread niceness values corresponding to the game threads' Windows base -priority levels. However, most default Linux configurations don't allow -individual threads to raise their priority, so some system configuration is -likely required. +priority levels. Additionally, Proton includes IO priority optimizations to +improve game responsiveness during heavy IO activity. + +## Thread Priority Configuration + +Most default Linux configurations don't allow individual threads to raise their +priority, so some system configuration is likely required. It can be configured as a privileged user by editing the `/etc/security/limits.conf` file, or using the `/etc/security/limits.d/` conf @@ -18,3 +22,53 @@ Where -15 could be any value between [-20,0] that will correspond to the minimum niceness (the highest priority) a thread can get to. The lower the value, the more CPU time a high priority thread will get, at the expense of others and other processes, possibly making the system less responsive. + +## IO Priority Optimization + +Proton automatically configures IO scheduling and CPU priorities to improve game +responsiveness during heavy IO operations (such as screen recording, file +downloads, or system backups). This feature is enabled by default and can help +prevent games from becoming unresponsive during intensive disk activity. + +### How it works + +- **IO Priority**: Sets games to use the "best-effort" IO scheduling class with + high priority (priority 2 by default), ensuring games get preferential access + to disk resources during heavy IO operations. + +- **CPU Priority**: Sets a slightly higher CPU scheduling priority (nice value -2) + to give games a small advantage in CPU scheduling during IO-intensive periods. + +### Environment Variables + +- `PROTON_ENABLE_IO_PRIORITY`: Enable/disable IO priority optimizations. + Set to `0` to disable, `1` to enable (default). + +- `PROTON_IO_PRIORITY`: IO scheduling priority (0-7, lower = higher priority). + Default is `2` (high priority). Only used when IO priority is enabled. + +- `PROTON_NICE_VALUE`: CPU scheduling priority (-20 to 19, lower = higher priority). + Default is `-2` (slightly higher priority). Requires proper limits.conf configuration. + +### Usage Examples + +```bash +# Disable IO priority optimization entirely +PROTON_ENABLE_IO_PRIORITY=0 %command% + +# Use maximum IO priority (may affect system responsiveness) +PROTON_IO_PRIORITY=0 %command% + +# Use normal CPU priority but keep IO priority optimization +PROTON_NICE_VALUE=0 %command% + +# Conservative settings for shared systems +PROTON_IO_PRIORITY=4 PROTON_NICE_VALUE=0 %command% +``` + +### Troubleshooting + +If you experience system unresponsiveness while gaming during heavy IO operations, +you can adjust or disable these optimizations. Conservative values or disabling +the feature entirely may be necessary on systems with limited resources or +specific workload requirements. diff --git a/proton b/proton index c0e0c43687..6b6ed741f1 100755 --- a/proton +++ b/proton @@ -1786,7 +1786,72 @@ class Session: def run_proc(self, args, local_env=None): if local_env is None: local_env = self.env - return subprocess.call(args, env=local_env, stderr=self.log_file, stdout=self.log_file) + + # Check if IO priority optimization is enabled (default: enabled) + enable_io_priority = os.environ.get("PROTON_ENABLE_IO_PRIORITY", "1") == "1" + + # Set up IO and CPU scheduling for better performance during heavy IO activity + def preexec_fn(): + if not enable_io_priority: + return + + try: + # Set IO scheduling class to best-effort with high priority (lower value = higher priority) + # This helps games maintain responsiveness during heavy IO operations like recording + # ioprio_set(IOPRIO_WHO_PROCESS, pid, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, priority)) + # IOPRIO_CLASS_BE = 1, priority 2 (range 0-7, 0=highest) + import ctypes + libc = ctypes.CDLL("libc.so.6", use_errno=True) + + # Constants for ioprio_set + IOPRIO_WHO_PROCESS = 1 + IOPRIO_CLASS_BE = 1 # Best-effort scheduling class + + def IOPRIO_PRIO_VALUE(ioprio_class, ioprio_data): + return (ioprio_class << 13) | ioprio_data + + # Allow user to customize IO priority (default: 2 = high priority) + io_priority = int(os.environ.get("PROTON_IO_PRIORITY", "2")) + if io_priority < 0 or io_priority > 7: + io_priority = 2 # Fallback to safe default + + # Set IO priority to best-effort class with specified priority + ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, io_priority) + result = libc.ioprio_set(IOPRIO_WHO_PROCESS, 0, ioprio) + if result != 0: + # If ioprio_set fails, it's not critical - log and continue + errno = ctypes.get_errno() + log(f"Warning: Could not set IO priority (errno: {errno}). Continuing anyway.") + + # Set CPU scheduler priority (nice value) to slightly higher than normal + # This gives games a small CPU scheduling advantage during heavy IO + try: + # Allow user to customize nice value (default: -2 = slightly higher priority) + nice_value = int(os.environ.get("PROTON_NICE_VALUE", "-2")) + if nice_value < -20 or nice_value > 19: + nice_value = -2 # Fallback to safe default + + os.setpriority(os.PRIO_PROCESS, 0, nice_value) + except OSError as e: + # If we can't set negative nice (higher priority), try positive nice + if e.errno == 1: # EPERM - Operation not permitted + try: + # Set nice value to 0 (normal priority) as fallback + os.setpriority(os.PRIO_PROCESS, 0, 0) + except OSError: + # If even that fails, continue without changing priority + log("Warning: Could not adjust process priority. Consider configuring /etc/security/limits.conf for better performance.") + else: + log(f"Warning: Could not set process priority: {e}") + + except Exception as e: + # Don't fail the whole process if priority setting fails + log(f"Warning: Could not configure process scheduling: {e}") + + if enable_io_priority: + return subprocess.call(args, env=local_env, stderr=self.log_file, stdout=self.log_file, preexec_fn=preexec_fn) + else: + return subprocess.call(args, env=local_env, stderr=self.log_file, stdout=self.log_file) def run(self): if shutil.which('steam-runtime-launcher-interface-0') is not None: