// // // Copyright (c) 2012, Daniel Cornel. Published on drivenbynostalgia.com. // 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 the copyright holder 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 HOLDER 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. // // namespace SOP { using System; using System.Runtime.InteropServices; /// /// This static class is used to set or remove an Nvidia profile with Optimus /// setting such that the discrete GPU is used for the specified application. /// public static class SOP { // Return values public const int RESULT_NO_CHANGE = 0; public const int RESULT_CHANGE = 1; public const int RESULT_ERROR = -1; // Descriptor for an application bound to an application profile [StructLayout(LayoutKind.Sequential, Pack = 8)] private unsafe struct Application { public uint version; public uint isPredefined; public fixed ushort appName[2048]; public fixed ushort userFriendlyName[2048]; public fixed ushort launcher[2048]; public fixed ushort fileInFolder[2048]; }; // Application profile descriptor [StructLayout(LayoutKind.Sequential, Pack = 8)] private unsafe struct Profile { public uint version; public fixed ushort profileName[2048]; public uint* gpuSupport; public uint isPredefined; public uint numOfApps; public uint numOfSettings; }; // Setting descriptor [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] private struct Setting { [FieldOffset(0)] public uint version; [FieldOffset(4), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2048)] public string settingName; [FieldOffset(4100)] public uint settingID; [FieldOffset(4104)] public uint settingType; [FieldOffset(4108)] public uint settingLocation; [FieldOffset(4112)] public uint isCurrentPredefined; [FieldOffset(4116)] public uint isPredefinedValid; // Convert the C unions with explicit field offsets [FieldOffset(4120)] public uint u32PredefinedValue; [FieldOffset(8220)] public uint u32CurrentValue; }; [DllImport("kernel32.dll")] private static extern IntPtr LoadLibrary(string dllToLoad); // Definitions for required NvAPI functions [DllImport("nvapi.dll", EntryPoint = "nvapi_QueryInterface", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr QueryInterface(uint offset); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int CreateSessionDelegate(out IntPtr session); private static CreateSessionDelegate CreateSession; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int CreateApplicationDelegate(IntPtr session, IntPtr profile, ref Application application); private static CreateApplicationDelegate CreateApplication; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int CreateProfileDelegate(IntPtr session, ref Profile profileInfo, out IntPtr profile); private static CreateProfileDelegate CreateProfile; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int DeleteProfileDelegate(IntPtr session, IntPtr profile); private static DeleteProfileDelegate DeleteProfile; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int DestroySessionDelegate(IntPtr session); private static DestroySessionDelegate DestroySession; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private unsafe delegate int EnumApplicationsDelegate(IntPtr session, IntPtr profile, uint startIndex, ref uint appCount, Application* allApplications); private static EnumApplicationsDelegate EnumApplications; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int FindProfileByNameDelegate(IntPtr session, [MarshalAs(UnmanagedType.BStr)] string profileName, out IntPtr profile); private static FindProfileByNameDelegate FindProfileByName; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int GetProfileInfoDelegate(IntPtr session, IntPtr profile, ref Profile profileInfo); private static GetProfileInfoDelegate GetProfileInfo; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int InitializeDelegate(); private static InitializeDelegate Initialize; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int LoadSettingsDelegate(IntPtr session); private static LoadSettingsDelegate LoadSettings; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SaveSettingsDelegate(IntPtr session); private static SaveSettingsDelegate SaveSettings; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SetSettingDelegate(IntPtr session, IntPtr profile, ref Setting setting); private static SetSettingDelegate SetSetting; private static bool CheckForError(int status) { if (status != 0) { #if DEBUG Console.WriteLine("NvAPI error in SetOptimusProfile: " + status); #endif return true; } else { return false; } } private static unsafe bool UnicodeStringCompare(ushort* unicodeString, ushort[] referenceString) { for (int i = 0; i < 2048; i++) { if (unicodeString[i] != referenceString[i]) { return false; } } return true; } private static ushort[] GetUnicodeString(string sourceString) { ushort[] destinationString = new ushort[2048]; for (int i = 0; i < 2048; i++) { if (i < sourceString.Length) { destinationString[i] = Convert.ToUInt16(sourceString[i]); } else { destinationString[i] = 0; } } return destinationString; } private static bool GetProcs() { // Check if this is a 32 bit application if (IntPtr.Size != 4) { #if DEBUG Console.WriteLine("Only 32 bit applications are supported."); #endif return false; } // Check if the nvapi.dll is available if (LoadLibrary("nvapi.dll") == IntPtr.Zero) { #if DEBUG Console.WriteLine("The nvapi.dll could not be found."); #endif return false; } try { CreateApplication = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x4347A9DE), typeof(CreateApplicationDelegate)) as CreateApplicationDelegate; CreateProfile = Marshal.GetDelegateForFunctionPointer(QueryInterface(0xCC176068), typeof(CreateProfileDelegate)) as CreateProfileDelegate; CreateSession = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x0694D52E), typeof(CreateSessionDelegate)) as CreateSessionDelegate; DeleteProfile = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x17093206), typeof(DeleteProfileDelegate)) as DeleteProfileDelegate; DestroySession = Marshal.GetDelegateForFunctionPointer(QueryInterface(0xDAD9CFF8), typeof(DestroySessionDelegate)) as DestroySessionDelegate; EnumApplications = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x7FA2173A), typeof(EnumApplicationsDelegate)) as EnumApplicationsDelegate; FindProfileByName = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x7E4A9A0B), typeof(FindProfileByNameDelegate)) as FindProfileByNameDelegate; GetProfileInfo = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x61CD6FD6), typeof(GetProfileInfoDelegate)) as GetProfileInfoDelegate; Initialize = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x0150E828), typeof(InitializeDelegate)) as InitializeDelegate; LoadSettings = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x375DBD6B), typeof(LoadSettingsDelegate)) as LoadSettingsDelegate; SaveSettings = Marshal.GetDelegateForFunctionPointer(QueryInterface(0xFCBC7E14), typeof(SaveSettingsDelegate)) as SaveSettingsDelegate; SetSetting = Marshal.GetDelegateForFunctionPointer(QueryInterface(0x577DD202), typeof(SetSettingDelegate)) as SetSettingDelegate; } catch (Exception) { #if DEBUG Console.WriteLine("The procs of nvapi.dll could not be retrieved."); #endif return false; } return true; } private static unsafe bool ContainsApplication(IntPtr session, IntPtr profile, Profile profileDescriptor, ushort[] unicodeApplicationName, out Application application) { application = new Application(); if (profileDescriptor.numOfApps == 0) { return false; } // Iterate over all applications bound to the profile Application[] allApplications = new Application[profileDescriptor.numOfApps]; uint numAppsRead = profileDescriptor.numOfApps; fixed (Application* allApplicationsPointer = allApplications) { allApplicationsPointer[0].version = 147464; // Calculated from the size of the descriptor if (CheckForError(EnumApplications(session, profile, 0, ref numAppsRead, allApplicationsPointer))) { return false; } for (uint i = 0; i < numAppsRead; i++) { if (UnicodeStringCompare(allApplicationsPointer[i].appName, unicodeApplicationName)) { application = allApplicationsPointer[i]; return true; } } } return false; } // Call this from your application to check if an application profile with // the name provided exists. public static bool SOP_CheckProfile(string profileName) { bool result = false; IntPtr session; IntPtr profile; // Initialize NvAPI if ((!GetProcs()) || (CheckForError(Initialize()))) { return false; } // Create the session handle to access driver settings if (CheckForError(CreateSession(out session))) { return false; } // Load all the system settings into the session if (CheckForError(LoadSettings(session))) { return false; } // Convert the profile name to a unicode string array ushort[] unicodeProfileName = GetUnicodeString(profileName); // Check if the application profile with the specified name exists result = (FindProfileByName(session, profileName, out profile) == 0); DestroySession(session); return result; } // Call this from your application to delete the application profile with // the name provided. Note that only one application profile per name exists // for all applications bound to it. // After the profile has been erased, the application will use the default GPU // the next time it is started, usually the integrated GPU. public static int SOP_RemoveProfile(string profileName) { int result = SOP.RESULT_NO_CHANGE; int status = 0; IntPtr session; IntPtr profile; // Initialize NvAPI if ((!GetProcs()) || (CheckForError(Initialize()))) { return SOP.RESULT_ERROR; } // Create the session handle to access driver settings if (CheckForError(CreateSession(out session))) { return SOP.RESULT_ERROR; } // Load all the system settings into the session if (CheckForError(LoadSettings(session))) { return SOP.RESULT_ERROR; } // Convert the profile name to a unicode string array ushort[] unicodeProfileName = GetUnicodeString(profileName); // Check if the application profile with the specified name already exists status = FindProfileByName(session, profileName, out profile); if (status == 0) { // The application profile with the specified name exists and can be deleted if ((CheckForError(DeleteProfile(session, profile))) || CheckForError(SaveSettings(session))) { return SOP.RESULT_ERROR; } else { result = SOP.RESULT_CHANGE; } } else if (status == -163 /* Profile not found */) { // The application profile does not exist and does not have to be deleted result = SOP.RESULT_NO_CHANGE; } else { return SOP.RESULT_ERROR; } status = DestroySession(session); return result; } // Call this from your application to create a generic application profile with // the name provided and add the application name to it. The application profile is // set to start all bound applications with the discrete (NVIDIA) GPU. If the profile // already exists because it is shared among several applications, the provided // application name is bound to the existing profile. // The changes take effect the next time the application is started. public static int SOP_SetProfile(string profileName, string applicationName) { int result = SOP.RESULT_NO_CHANGE; int status = 0; IntPtr session; IntPtr profile; // Initialize NvAPI if ((!GetProcs()) || (CheckForError(Initialize()))) { return SOP.RESULT_ERROR; } // Create the session handle to access driver settings if (CheckForError(CreateSession(out session))) { return SOP.RESULT_ERROR; } // Load all the system settings into the session if (CheckForError(LoadSettings(session))) { return SOP.RESULT_ERROR; } // Convert the profile name to a unicode string array ushort[] unicodeProfileName = GetUnicodeString(profileName); // Convert the application name to a unicode string array ushort[] unicodeApplicationName = GetUnicodeString(applicationName); // Check if the application profile with the specified name already exists status = FindProfileByName(session, profileName, out profile); if (status == -163 /* Profile not found */) { // The application profile does not yet exist and has to be created Profile newProfileDescriptor = new Profile(); newProfileDescriptor.version = 69652; // Calculated from the size of the descriptor newProfileDescriptor.isPredefined = 0; unsafe { for (int i = 0; i < 2048; i++) { newProfileDescriptor.profileName[i] = unicodeProfileName[i]; } fixed (uint* gpuSupport = new uint[32]) { newProfileDescriptor.gpuSupport = gpuSupport; newProfileDescriptor.gpuSupport[0] = 1; } } // Create the application profile if (CheckForError(CreateProfile(session, ref newProfileDescriptor, out profile))) { return SOP.RESULT_ERROR; } // Create the application settings. This is where the discrete GPU for Optimus is set. Setting optimusSetting = new Setting(); optimusSetting.version = 77856; // Calculated from the size of the descriptor optimusSetting.settingID = 0x10F9DC81; // Shim rendering mode ID optimusSetting.u32CurrentValue = 0x00000001 | 0x00000010; // Enable | Auto select if (CheckForError(SetSetting(session, profile, ref optimusSetting))) { return SOP.RESULT_ERROR; } } else if (CheckForError(status)) { return SOP.RESULT_ERROR; } // Retrieve the profile information of the application profile Profile profileDescriptorManaged = new Profile(); profileDescriptorManaged.version = 69652; // Calculated from the size of the descriptor if (CheckForError(GetProfileInfo(session, profile, ref profileDescriptorManaged))) { return SOP.RESULT_ERROR; } // Application descriptor Application applicationDescriptor = new Application(); if (!ContainsApplication(session, profile, profileDescriptorManaged, GetUnicodeString(applicationName.ToLower()), out applicationDescriptor)) { applicationDescriptor.version = 147464; // Calculated from the size of the descriptor applicationDescriptor.isPredefined = 0; unsafe { for (int i = 0; i < 2048; i++) { applicationDescriptor.appName[i] = unicodeApplicationName[i]; } // Add the current application to the new profile if ((CheckForError(CreateApplication(session, profile, ref applicationDescriptor))) || (CheckForError(SaveSettings(session)))) { return SOP.RESULT_ERROR; } else { result = SOP.RESULT_CHANGE; } } } status = DestroySession(session); return result; } } }