Browse Source

Merge pull request #179 from jekstrand/igpu

Add an option for using a different governor for integrated GPUs
afayaz-feral 5 years ago
parent
commit
1576c2b39e
8 changed files with 459 additions and 42 deletions
  1. 5 0
      bootstrap.sh
  2. 165 0
      common/common-power.c
  3. 45 0
      common/common-power.h
  4. 1 0
      common/meson.build
  5. 61 0
      daemon/gamemode-config.c
  6. 2 0
      daemon/gamemode-config.h
  7. 170 41
      daemon/gamemode-context.c
  8. 10 1
      example/gamemode.ini

+ 5 - 0
bootstrap.sh

@@ -40,5 +40,10 @@ set -x
 
 sudo ninja install
 
+# Restart polkit so we don't get pop-ups whenever we pkexec
+if systemctl list-unit-files |grep -q polkit.service; then
+    sudo systemctl try-restart polkit
+fi
+
 # Reload systemd configuration so that it picks up the new service.
 systemctl --user daemon-reload

+ 165 - 0
common/common-power.c

@@ -0,0 +1,165 @@
+/*
+
+Copyright (c) 2017-2019, Feral Interactive
+Copyright (c) 2019, Intel Corporation
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of Feral Interactive nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+#define _GNU_SOURCE
+
+#include "common-power.h"
+#include "common-logging.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <glob.h>
+#include <linux/limits.h>
+#include <stdio.h>
+#include <string.h>
+
+static bool read_file_in_dir(const char *dir, const char *file, char *dest, size_t n)
+{
+	char path[PATH_MAX];
+	int ret = snprintf(path, sizeof(path), "%s/%s", dir, file);
+	if (ret < 0 || ret >= (int)sizeof(path)) {
+		LOG_ERROR("Path length overrun");
+		return false;
+	}
+
+	FILE *f = fopen(path, "r");
+	if (!f) {
+		LOG_ERROR("Failed to open file for read %s\n", path);
+		return false;
+	}
+
+	size_t read = fread(dest, 1, n, f);
+
+	/* Close before we do any error checking */
+	fclose(f);
+
+	if (read <= 0) {
+		LOG_ERROR("Failed to read contents of %s: (%s)\n", path, strerror(errno));
+		return false;
+	}
+
+	if (read >= n) {
+		LOG_ERROR("File contained more data than expected %s\n", path);
+		return false;
+	}
+
+	/* Ensure we're null terminated */
+	dest[read] = '\0';
+
+	/* Trim whitespace off the end */
+	while (read > 0 && isspace(dest[read - 1])) {
+		dest[read - 1] = '\0';
+		read--;
+	}
+
+	return true;
+}
+
+static bool get_energy_uj(const char *rapl_name, uint32_t *energy_uj)
+{
+	glob_t glo = { 0 };
+	static const char *path = "/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:*";
+
+	/* Assert some sanity on this glob */
+	if (glob(path, GLOB_NOSORT, NULL, &glo) != 0) {
+		LOG_ERROR("glob failed for RAPL paths: (%s)\n", strerror(errno));
+		return false;
+	}
+
+	/* If the glob doesn't find anything, this most likely means we don't
+	 * have an Intel CPU or we have a kernel which does not support RAPL on
+	 * our CPU.
+	 */
+	if (glo.gl_pathc < 1) {
+		LOG_ONCE(MSG,
+		         "Intel RAPL interface not found in sysfs. "
+		         "This is only problematic if you expected Intel iGPU "
+		         "power threshold optimization.");
+		globfree(&glo);
+		return false;
+	}
+
+	/* Walk the glob set */
+	for (size_t i = 0; i < glo.gl_pathc; i++) {
+		char name[32];
+		if (!read_file_in_dir(glo.gl_pathv[i], "name", name, sizeof(name))) {
+			return false;
+		}
+
+		/* We're searching for the directory where the file named "name"
+		 * contains the contents rapl_name. */
+		if (strncmp(name, rapl_name, sizeof(name)) != 0) {
+			continue;
+		}
+
+		char energy_uj_str[32];
+		if (!read_file_in_dir(glo.gl_pathv[i], "energy_uj", energy_uj_str, sizeof(energy_uj_str))) {
+			return false;
+		}
+
+		char *end = NULL;
+		long long energy_uj_ll = strtoll(energy_uj_str, &end, 10);
+		if (end == energy_uj_str) {
+			LOG_ERROR("Invalid energy_uj contents: %s\n", energy_uj_str);
+			return false;
+		}
+
+		if (energy_uj_ll < 0) {
+			LOG_ERROR("Value of energy_uj is out of expected bounds: %lld\n", energy_uj_ll);
+			return false;
+		}
+
+		/* Go ahead and clamp to 32 bits.  We assume 32 bits later when
+		 * taking deltas and wrapping at 32 bits is exactly what the Linux
+		 * kernel's turbostat utility does so it's probably right.
+		 */
+		*energy_uj = (uint32_t)energy_uj_ll;
+		return true;
+	}
+
+	/* If we got here then the CPU and Kernel support RAPL and all our file
+	 * access has succeeded but we failed to find an entry with the right
+	 * name.  This most likely means we're asking for "uncore" but are on a
+	 * machine that doesn't have an integrated GPU.
+	 */
+	return false;
+}
+
+bool get_cpu_energy_uj(uint32_t *energy_uj)
+{
+	return get_energy_uj("core", energy_uj);
+}
+
+bool get_igpu_energy_uj(uint32_t *energy_uj)
+{
+	return get_energy_uj("uncore", energy_uj);
+}

+ 45 - 0
common/common-power.h

@@ -0,0 +1,45 @@
+/*
+
+Copyright (c) 2017-2019, Feral Interactive
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of Feral Interactive nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Get the amount of energy used to date by the CPU in microjoules
+ */
+bool get_cpu_energy_uj(uint32_t *energy_uj);
+
+/**
+ * Get the amount of energy used to date by the integrated GPU in microjoules
+ */
+bool get_igpu_energy_uj(uint32_t *energy_uj);

+ 1 - 0
common/meson.build

@@ -6,6 +6,7 @@ common_sources = [
     'common-helpers.c',
     'common-gpu.c',
     'common-pidfds.c',
+    'common-power.c',
 ]
 
 daemon_common = static_library(

+ 61 - 0
daemon/gamemode-config.c

@@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE.
 #include "ini.h"
 
 #include <dirent.h>
+#include <math.h>
 #include <pthread.h>
 #include <pwd.h>
 #include <sys/inotify.h>
@@ -50,6 +51,8 @@ POSSIBILITY OF SUCH DAMAGE.
 /* Default value for the reaper frequency */
 #define DEFAULT_REAPER_FREQ 5
 
+#define DEFAULT_IGPU_POWER_THRESHOLD 0.3f
+
 /* Helper macro for defining the config variable getter */
 #define DEFINE_CONFIG_GET(name)                                                                    \
 	long config_get_##name(GameModeConfig *self)                                                   \
@@ -82,6 +85,9 @@ struct GameModeConfig {
 		char defaultgov[CONFIG_VALUE_MAX];
 		char desiredgov[CONFIG_VALUE_MAX];
 
+		char igpu_desiredgov[CONFIG_VALUE_MAX];
+		float igpu_power_threshold;
+
 		char softrealtime[CONFIG_VALUE_MAX];
 		long renice;
 
@@ -179,6 +185,27 @@ __attribute__((unused)) static bool get_long_value_hex(const char *value_name, c
 	return true;
 }
 
+/*
+ * Get a long value from a string
+ */
+static bool get_float_value(const char *value_name, const char *value, float *output)
+{
+	char *end = NULL;
+	float config_value = strtof(value, &end);
+
+	if (errno == ERANGE) {
+		LOG_ERROR("Config: %s overflowed, given [%s]\n", value_name, value);
+		return false;
+	} else if (!(*value != '\0' && end && *end == '\0')) {
+		LOG_ERROR("Config: %s was invalid, given [%s]\n", value_name, value);
+		return false;
+	} else {
+		*output = config_value;
+	}
+
+	return true;
+}
+
 /**
  * Simple strstr scheck
  * Could be expanded for wildcard or regex
@@ -230,6 +257,10 @@ static int inih_handler(void *user, const char *section, const char *name, const
 			valid = get_string_value(value, self->values.defaultgov);
 		} else if (strcmp(name, "desiredgov") == 0) {
 			valid = get_string_value(value, self->values.desiredgov);
+		} else if (strcmp(name, "igpu_desiredgov") == 0) {
+			valid = get_string_value(value, self->values.igpu_desiredgov);
+		} else if (strcmp(name, "igpu_power_threshold") == 0) {
+			valid = get_float_value(name, value, &self->values.igpu_power_threshold);
 		} else if (strcmp(name, "softrealtime") == 0) {
 			valid = get_string_value(value, self->values.softrealtime);
 		} else if (strcmp(name, "renice") == 0) {
@@ -329,6 +360,7 @@ static void load_config_files(GameModeConfig *self)
 	memset(&self->values, 0, sizeof(self->values));
 
 	/* Set some non-zero defaults */
+	self->values.igpu_power_threshold = DEFAULT_IGPU_POWER_THRESHOLD;
 	self->values.inhibit_screensaver = 1; /* Defaults to on */
 	self->values.reaper_frequency = DEFAULT_REAPER_FREQ;
 	self->values.gpu_device = -1; /* 0 is a valid device ID so use -1 to indicate no value */
@@ -641,6 +673,35 @@ void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALU
 	memcpy_locked_config(self, governor, self->values.desiredgov, sizeof(self->values.desiredgov));
 }
 
+/*
+ * Get the chosen iGPU desired governor
+ */
+void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
+{
+	memcpy_locked_config(self,
+	                     governor,
+	                     self->values.igpu_desiredgov,
+	                     sizeof(self->values.igpu_desiredgov));
+}
+
+/*
+ * Get the chosen iGPU power threshold
+ */
+float config_get_igpu_power_threshold(GameModeConfig *self)
+{
+	float value = 0;
+	memcpy_locked_config(self, &value, &self->values.igpu_power_threshold, sizeof(float));
+	/* Validate the threshold value */
+	if (isnan(value) || value < 0) {
+		LOG_ONCE(ERROR,
+		         "Configured iGPU power threshold value '%f' is invalid, ignoring iGPU default "
+		         "governor.\n",
+		         value);
+		value = FP_INFINITE;
+	}
+	return value;
+}
+
 /*
  * Get the chosen soft realtime behavior
  */

+ 2 - 0
daemon/gamemode-config.h

@@ -102,6 +102,8 @@ bool config_get_inhibit_screensaver(GameModeConfig *self);
 long config_get_script_timeout(GameModeConfig *self);
 void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
 void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
+void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
+float config_get_igpu_power_threshold(GameModeConfig *self);
 void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]);
 long config_get_renice_value(GameModeConfig *self);
 long config_get_ioprio_value(GameModeConfig *self);

+ 170 - 41
daemon/gamemode-context.c

@@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE.
 #include "common-governors.h"
 #include "common-helpers.h"
 #include "common-logging.h"
+#include "common-power.h"
 
 #include "gamemode.h"
 #include "gamemode-config.h"
@@ -63,6 +64,12 @@ struct GameModeClient {
 	time_t timestamp;            /**<When was the client registered */
 };
 
+enum GameModeGovernor {
+	GAME_MODE_GOVERNOR_DEFAULT,
+	GAME_MODE_GOVERNOR_DESIRED,
+	GAME_MODE_GOVERNOR_IGPU_DESIRED,
+};
+
 struct GameModeContext {
 	pthread_rwlock_t rwlock; /**<Guard access to the client list */
 	_Atomic int refcount;    /**<Allow cycling the game mode */
@@ -72,9 +79,15 @@ struct GameModeContext {
 
 	char initial_cpu_mode[64]; /**<Only updates when we can */
 
+	enum GameModeGovernor current_govenor;
+
 	struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */
 	struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */
 
+	bool igpu_optimization_enabled;
+	uint32_t last_cpu_energy_uj;
+	uint32_t last_igpu_energy_uj;
+
 	/* Reaper control */
 	struct {
 		pthread_t thread;
@@ -126,6 +139,8 @@ void game_mode_context_init(GameModeContext *self)
 	self->config = config_create();
 	config_init(self->config);
 
+	self->current_govenor = GAME_MODE_GOVERNOR_DEFAULT;
+
 	/* Initialise the current GPU info */
 	game_mode_initialise_gpu(self->config, &self->stored_gpu);
 	game_mode_initialise_gpu(self->config, &self->target_gpu);
@@ -178,6 +193,151 @@ void game_mode_context_destroy(GameModeContext *self)
 	pthread_rwlock_destroy(&self->rwlock);
 }
 
+static int game_mode_set_governor(GameModeContext *self, enum GameModeGovernor gov)
+{
+	if (self->current_govenor == gov) {
+		return 0;
+	}
+
+	if (self->current_govenor == GAME_MODE_GOVERNOR_DEFAULT) {
+		/* Read the initial governor state so we can revert it correctly */
+		const char *initial_state = get_gov_state();
+		if (initial_state == NULL) {
+			return 0;
+		}
+
+		/* store the initial cpu governor mode */
+		strncpy(self->initial_cpu_mode, initial_state, sizeof(self->initial_cpu_mode) - 1);
+		self->initial_cpu_mode[sizeof(self->initial_cpu_mode) - 1] = '\0';
+		LOG_MSG("governor was initially set to [%s]\n", initial_state);
+	}
+
+	char *gov_str = NULL;
+	char gov_config_str[CONFIG_VALUE_MAX] = { 0 };
+	switch (gov) {
+	case GAME_MODE_GOVERNOR_DEFAULT:
+		config_get_default_governor(self->config, gov_config_str);
+		gov_str = gov_config_str[0] != '\0' ? gov_config_str : self->initial_cpu_mode;
+		break;
+
+	case GAME_MODE_GOVERNOR_DESIRED:
+		config_get_desired_governor(self->config, gov_config_str);
+		gov_str = gov_config_str[0] != '\0' ? gov_config_str : "performance";
+		break;
+
+	case GAME_MODE_GOVERNOR_IGPU_DESIRED:
+		config_get_igpu_desired_governor(self->config, gov_config_str);
+		gov_str = gov_config_str[0] != '\0' ? gov_config_str : "powersave";
+		break;
+
+	default:
+		assert(!"Invalid governor requested");
+	}
+
+	const char *const exec_args[] = {
+		"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", gov_str, NULL,
+	};
+
+	LOG_MSG("Requesting update of governor policy to %s\n", gov_str);
+	int ret = run_external_process(exec_args, NULL, -1);
+	if (ret != 0) {
+		LOG_ERROR("Failed to update cpu governor policy\n");
+		return ret;
+	}
+
+	/* Update the current govenor only if we succeed at setting govenors. */
+	self->current_govenor = gov;
+
+	return 0;
+}
+
+static void game_mode_enable_igpu_optimization(GameModeContext *self)
+{
+	float threshold = config_get_igpu_power_threshold(self->config);
+
+	/* There's no way the GPU is using 10000x the power.  This lets us
+	 * short-circuit if the config file specifies an invalid threshold
+	 * and we want to disable the iGPU heuristic.
+	 */
+	if (threshold < 10000 && get_cpu_energy_uj(&self->last_cpu_energy_uj) &&
+	    get_igpu_energy_uj(&self->last_igpu_energy_uj)) {
+		LOG_MSG(
+		    "Successfully queried power data for the CPU and iGPU. "
+		    "Enabling the integrated GPU optimization");
+		self->igpu_optimization_enabled = true;
+	}
+}
+
+static void game_mode_disable_igpu_optimization(GameModeContext *self)
+{
+	self->igpu_optimization_enabled = false;
+}
+
+static void game_mode_check_igpu_energy(GameModeContext *self)
+{
+	pthread_rwlock_wrlock(&self->rwlock);
+
+	/* We only care if we're not in the default governor */
+	if (self->current_govenor == GAME_MODE_GOVERNOR_DEFAULT)
+		goto unlock;
+
+	if (!self->igpu_optimization_enabled)
+		goto unlock;
+
+	uint32_t cpu_energy_uj, igpu_energy_uj;
+	if (!get_cpu_energy_uj(&cpu_energy_uj) || !get_igpu_energy_uj(&igpu_energy_uj)) {
+		/* We've already succeeded at getting power information once so
+		 * failing here is possible but very unexpected. */
+		self->igpu_optimization_enabled = false;
+		LOG_ERROR("Failed to get CPU and iGPU power data\n");
+		goto unlock;
+	}
+
+	/* The values we query from RAPL are in units of microjoules of energy
+	 * used since boot or since the last time the counter rolled over.  You
+	 * can get average power over some time window T by sampling before and
+	 * after and doing the following calculation
+	 *
+	 *     power_uw = (energy_uj_after - energy_uj_before) / seconds
+	 *
+	 * To get the power in Watts (rather than microwatts), you can simply
+	 * divide by 1000000.
+	 *
+	 * Because we're only concerned with the ratio between the GPU and CPU
+	 * power, we never bother dividing by 1000000 the length of time of the
+	 * sampling window because that would just algebraically cancel out.
+	 * Instead, we divide the GPU energy used in the window (difference of
+	 * before and after) by the CPU energy used. It nicely provides the
+	 * ratio of the averages and there are no instantaneous sampling
+	 * problems.
+	 *
+	 * Overflow is possible here.  However, that would simply mean that
+	 * the HW counter has overflowed and us wrapping around is probably
+	 * the right thing to do.  Wrapping at 32 bits is exactly what the
+	 * Linux kernel's turbostat utility does so it's probably right.
+	 */
+	uint32_t cpu_energy_delta_uj = cpu_energy_uj - self->last_cpu_energy_uj;
+	uint32_t igpu_energy_delta_uj = igpu_energy_uj - self->last_igpu_energy_uj;
+	self->last_cpu_energy_uj = cpu_energy_uj;
+	self->last_igpu_energy_uj = igpu_energy_uj;
+
+	if (cpu_energy_delta_uj == 0) {
+		LOG_ERROR("CPU reported no energy used\n");
+		goto unlock;
+	}
+
+	float threshold = config_get_igpu_power_threshold(self->config);
+	double ratio = (double)igpu_energy_delta_uj / (double)cpu_energy_delta_uj;
+	if (ratio > threshold) {
+		game_mode_set_governor(self, GAME_MODE_GOVERNOR_IGPU_DESIRED);
+	} else {
+		game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED);
+	}
+
+unlock:
+	pthread_rwlock_unlock(&self->rwlock);
+}
+
 /**
  * Pivot into game mode.
  *
@@ -189,30 +349,11 @@ static void game_mode_context_enter(GameModeContext *self)
 	LOG_MSG("Entering Game Mode...\n");
 	sd_notifyf(0, "STATUS=%sGameMode is now active.%s\n", "\x1B[1;32m", "\x1B[0m");
 
-	/* Read the initial governor state so we can revert it correctly */
-	const char *initial_state = get_gov_state();
-	if (initial_state) {
-		/* store the initial cpu governor mode */
-		strncpy(self->initial_cpu_mode, initial_state, sizeof(self->initial_cpu_mode) - 1);
-		self->initial_cpu_mode[sizeof(self->initial_cpu_mode) - 1] = '\0';
-		LOG_MSG("governor was initially set to [%s]\n", initial_state);
-
-		/* Choose the desired governor */
-		char desired[CONFIG_VALUE_MAX] = { 0 };
-		config_get_desired_governor(self->config, desired);
-		const char *desiredGov = desired[0] != '\0' ? desired : "performance";
-
-		const char *const exec_args[] = {
-			"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", desiredGov, NULL,
-		};
-
-		LOG_MSG("Requesting update of governor policy to %s\n", desiredGov);
-		if (run_external_process(exec_args, NULL, -1) != 0) {
-			LOG_ERROR("Failed to update cpu governor policy\n");
-			/* if the set fails, clear the initial mode so we don't try and reset it back and fail
-			 * again, presumably */
-			memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
-		}
+	if (game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED) == 0) {
+		/* We just switched to a non-default governor.  Enable the iGPU
+		 * optimization.
+		 */
+		game_mode_enable_igpu_optimization(self);
 	}
 
 	/* Inhibit the screensaver */
@@ -250,24 +391,9 @@ static void game_mode_context_leave(GameModeContext *self)
 	if (config_get_inhibit_screensaver(self->config))
 		game_mode_inhibit_screensaver(false);
 
-	/* Reset the governer state back to initial */
-	if (self->initial_cpu_mode[0] != '\0') {
-		/* Choose the governor to reset to, using the config to override */
-		char defaultgov[CONFIG_VALUE_MAX] = { 0 };
-		config_get_default_governor(self->config, defaultgov);
-		const char *gov_mode = defaultgov[0] != '\0' ? defaultgov : self->initial_cpu_mode;
-
-		const char *const exec_args[] = {
-			"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", gov_mode, NULL,
-		};
+	game_mode_set_governor(self, GAME_MODE_GOVERNOR_DEFAULT);
 
-		LOG_MSG("Requesting update of governor policy to %s\n", gov_mode);
-		if (run_external_process(exec_args, NULL, -1) != 0) {
-			LOG_ERROR("Failed to update cpu governor policy\n");
-		}
-
-		memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
-	}
+	game_mode_disable_igpu_optimization(self);
 
 	char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
 	memset(scripts, 0, sizeof(scripts));
@@ -801,6 +927,9 @@ static void *game_mode_context_reaper(void *userdata)
 			return NULL;
 		}
 
+		/* Check on the CPU/iGPU energy balance */
+		game_mode_check_igpu_energy(self);
+
 		/* Expire remaining entries */
 		game_mode_context_auto_expire(self);
 

+ 10 - 1
example/gamemode.ini

@@ -1,5 +1,5 @@
 [general]
-; The reaper thread will check every 5 seconds for exited clients and for config file changes
+; The reaper thread will check every 5 seconds for exited clients, for config file changes, and for the CPU/iGPU power balance
 reaper_freq=5
 
 ; The desired governor is used when entering GameMode instead of "performance"
@@ -7,6 +7,15 @@ desiredgov=performance
 ; The default governer is used when leaving GameMode instead of restoring the original value
 defaultgov=powersave
 
+; The iGPU desired governor is used when the integrated GPU is under heavy load
+igpu_desiredgov=powersave
+; Threshold to use to decide when the integrated GPU is under heavy load.
+; This is a ratio of iGPU Watts / CPU Watts which is used to determine when the
+; integraged GPU is under heavy enough load to justify switching to
+; igpu_desiredgov.  Set this to -1 to disable all iGPU checking and always
+; use desiredgov for games.
+igpu_power_threshold=0.3
+
 ; GameMode can change the scheduler policy to SCHED_ISO on kernels which support it (currently
 ; not supported by upstream kernels). Can be set to "auto", "on" or "off". "auto" will enable
 ; with 4 or more CPU cores. "on" will always enable. Defaults to "off".