Added AutoTDP feature with RTSS source and Intel + ASUS power limiters

This commit is contained in:
IceStormNG
2024-03-01 07:47:00 +01:00
parent 7cc4e87d5f
commit 84a6fd4d5f
19 changed files with 2062 additions and 0 deletions

View File

@@ -0,0 +1,337 @@
namespace GHelper.AutoTDP
{
partial class AutoTDPGameProfileUI
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
panelPerformanceHeader = new Panel();
pictureKeyboard = new PictureBox();
labelSettings = new Label();
checkBoxEnabled = new CheckBox();
panelLightingContent = new Panel();
labelMinTDP = new Label();
labelMaxTDP = new Label();
sliderMaxTDP = new UI.Slider();
numericUpDownFPS = new NumericUpDown();
sliderMinTDP = new UI.Slider();
label2 = new Label();
label3 = new Label();
label1 = new Label();
textBoxTitle = new TextBox();
textBoxProcessName = new TextBox();
labelFPSSource = new Label();
labelLimiter = new Label();
buttonSave = new UI.RButton();
buttonDelete = new UI.RButton();
panelPerformanceHeader.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pictureKeyboard).BeginInit();
panelLightingContent.SuspendLayout();
((System.ComponentModel.ISupportInitialize)numericUpDownFPS).BeginInit();
SuspendLayout();
//
// panelPerformanceHeader
//
panelPerformanceHeader.BackColor = SystemColors.ControlLight;
panelPerformanceHeader.Controls.Add(pictureKeyboard);
panelPerformanceHeader.Controls.Add(labelSettings);
panelPerformanceHeader.Controls.Add(checkBoxEnabled);
panelPerformanceHeader.Dock = DockStyle.Top;
panelPerformanceHeader.Location = new Point(0, 0);
panelPerformanceHeader.Margin = new Padding(2);
panelPerformanceHeader.Name = "panelPerformanceHeader";
panelPerformanceHeader.Size = new Size(386, 30);
panelPerformanceHeader.TabIndex = 53;
//
// pictureKeyboard
//
pictureKeyboard.BackgroundImage = Properties.Resources.icons8_automation_32;
pictureKeyboard.BackgroundImageLayout = ImageLayout.Zoom;
pictureKeyboard.Location = new Point(3, 8);
pictureKeyboard.Margin = new Padding(2);
pictureKeyboard.Name = "pictureKeyboard";
pictureKeyboard.Size = new Size(16, 16);
pictureKeyboard.TabIndex = 35;
pictureKeyboard.TabStop = false;
//
// labelSettings
//
labelSettings.AutoSize = true;
labelSettings.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point);
labelSettings.Location = new Point(22, 8);
labelSettings.Margin = new Padding(4, 0, 4, 0);
labelSettings.Name = "labelSettings";
labelSettings.Size = new Size(89, 15);
labelSettings.TabIndex = 34;
labelSettings.Text = "Game Settings";
//
// checkBoxEnabled
//
checkBoxEnabled.Location = new Point(241, 2);
checkBoxEnabled.Margin = new Padding(4, 0, 4, 0);
checkBoxEnabled.Name = "checkBoxEnabled";
checkBoxEnabled.Size = new Size(136, 25);
checkBoxEnabled.TabIndex = 53;
checkBoxEnabled.Text = "Enabled";
checkBoxEnabled.UseVisualStyleBackColor = true;
//
// panelLightingContent
//
panelLightingContent.AutoSize = true;
panelLightingContent.Controls.Add(labelMinTDP);
panelLightingContent.Controls.Add(labelMaxTDP);
panelLightingContent.Controls.Add(sliderMaxTDP);
panelLightingContent.Controls.Add(numericUpDownFPS);
panelLightingContent.Controls.Add(sliderMinTDP);
panelLightingContent.Controls.Add(label2);
panelLightingContent.Controls.Add(label3);
panelLightingContent.Controls.Add(label1);
panelLightingContent.Controls.Add(textBoxTitle);
panelLightingContent.Controls.Add(textBoxProcessName);
panelLightingContent.Controls.Add(labelFPSSource);
panelLightingContent.Controls.Add(labelLimiter);
panelLightingContent.Dock = DockStyle.Top;
panelLightingContent.Location = new Point(0, 30);
panelLightingContent.Margin = new Padding(2);
panelLightingContent.Name = "panelLightingContent";
panelLightingContent.Padding = new Padding(0, 0, 0, 7);
panelLightingContent.Size = new Size(386, 146);
panelLightingContent.TabIndex = 57;
//
// labelMinTDP
//
labelMinTDP.Location = new Point(342, 91);
labelMinTDP.Name = "labelMinTDP";
labelMinTDP.Size = new Size(39, 22);
labelMinTDP.TabIndex = 67;
labelMinTDP.Text = "30W";
labelMinTDP.TextAlign = ContentAlignment.MiddleRight;
//
// labelMaxTDP
//
labelMaxTDP.Location = new Point(341, 117);
labelMaxTDP.Name = "labelMaxTDP";
labelMaxTDP.Size = new Size(40, 22);
labelMaxTDP.TabIndex = 66;
labelMaxTDP.Text = "150W";
labelMaxTDP.TextAlign = ContentAlignment.MiddleRight;
//
// sliderMaxTDP
//
sliderMaxTDP.AccessibleName = "DPI Slider";
sliderMaxTDP.Location = new Point(140, 117);
sliderMaxTDP.Margin = new Padding(2);
sliderMaxTDP.Max = 200;
sliderMaxTDP.Min = 5;
sliderMaxTDP.Name = "sliderMaxTDP";
sliderMaxTDP.Size = new Size(204, 20);
sliderMaxTDP.Step = 1;
sliderMaxTDP.TabIndex = 65;
sliderMaxTDP.TabStop = false;
sliderMaxTDP.Text = "sliderBattery";
sliderMaxTDP.Value = 0;
//
// numericUpDownFPS
//
numericUpDownFPS.BorderStyle = BorderStyle.None;
numericUpDownFPS.Location = new Point(291, 66);
numericUpDownFPS.Margin = new Padding(2);
numericUpDownFPS.Maximum = new decimal(new int[] { 1000, 0, 0, 0 });
numericUpDownFPS.Minimum = new decimal(new int[] { 20, 0, 0, 0 });
numericUpDownFPS.Name = "numericUpDownFPS";
numericUpDownFPS.Size = new Size(86, 19);
numericUpDownFPS.TabIndex = 64;
numericUpDownFPS.TextAlign = HorizontalAlignment.Center;
numericUpDownFPS.Value = new decimal(new int[] { 60, 0, 0, 0 });
//
// sliderMinTDP
//
sliderMinTDP.AccessibleName = "DPI Slider";
sliderMinTDP.Location = new Point(140, 93);
sliderMinTDP.Margin = new Padding(2);
sliderMinTDP.Max = 200;
sliderMinTDP.Min = 5;
sliderMinTDP.Name = "sliderMinTDP";
sliderMinTDP.Size = new Size(204, 20);
sliderMinTDP.Step = 1;
sliderMinTDP.TabIndex = 63;
sliderMinTDP.TabStop = false;
sliderMinTDP.Text = "sliderBattery";
sliderMinTDP.Value = 0;
//
// label2
//
label2.Location = new Point(5, 117);
label2.Margin = new Padding(4, 0, 4, 0);
label2.Name = "label2";
label2.Size = new Size(129, 22);
label2.TabIndex = 61;
label2.Text = "Max TDP:";
label2.TextAlign = ContentAlignment.MiddleLeft;
//
// label3
//
label3.Location = new Point(5, 91);
label3.Margin = new Padding(4, 0, 4, 0);
label3.Name = "label3";
label3.Size = new Size(129, 22);
label3.TabIndex = 62;
label3.Text = "Min TDP:";
label3.TextAlign = ContentAlignment.MiddleLeft;
//
// label1
//
label1.Location = new Point(5, 63);
label1.Margin = new Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new Size(129, 22);
label1.TabIndex = 60;
label1.Text = "Target FPS:";
label1.TextAlign = ContentAlignment.MiddleLeft;
//
// textBoxTitle
//
textBoxTitle.Location = new Point(140, 36);
textBoxTitle.Name = "textBoxTitle";
textBoxTitle.Size = new Size(237, 23);
textBoxTitle.TabIndex = 59;
//
// textBoxProcessName
//
textBoxProcessName.Location = new Point(140, 6);
textBoxProcessName.Name = "textBoxProcessName";
textBoxProcessName.ReadOnly = true;
textBoxProcessName.Size = new Size(237, 23);
textBoxProcessName.TabIndex = 58;
textBoxProcessName.WordWrap = false;
//
// labelFPSSource
//
labelFPSSource.Location = new Point(4, 37);
labelFPSSource.Margin = new Padding(4, 0, 4, 0);
labelFPSSource.Name = "labelFPSSource";
labelFPSSource.Size = new Size(129, 22);
labelFPSSource.TabIndex = 57;
labelFPSSource.Text = "Name:";
labelFPSSource.TextAlign = ContentAlignment.MiddleLeft;
//
// labelLimiter
//
labelLimiter.Location = new Point(4, 7);
labelLimiter.Margin = new Padding(4, 0, 4, 0);
labelLimiter.Name = "labelLimiter";
labelLimiter.Size = new Size(129, 22);
labelLimiter.TabIndex = 47;
labelLimiter.Text = "Process";
labelLimiter.TextAlign = ContentAlignment.MiddleLeft;
//
// buttonSave
//
buttonSave.AccessibleName = "Keyboard Color";
buttonSave.Activated = false;
buttonSave.Anchor = AnchorStyles.Top | AnchorStyles.Right;
buttonSave.BackColor = SystemColors.ButtonHighlight;
buttonSave.BorderColor = Color.Transparent;
buttonSave.BorderRadius = 2;
buttonSave.FlatStyle = FlatStyle.Flat;
buttonSave.ForeColor = SystemColors.ControlText;
buttonSave.Location = new Point(279, 182);
buttonSave.Margin = new Padding(2, 4, 2, 4);
buttonSave.Name = "buttonSave";
buttonSave.Secondary = false;
buttonSave.Size = new Size(103, 25);
buttonSave.TabIndex = 61;
buttonSave.Text = "Save";
buttonSave.UseVisualStyleBackColor = false;
//
// buttonDelete
//
buttonDelete.AccessibleName = "Keyboard Color";
buttonDelete.Activated = false;
buttonDelete.Anchor = AnchorStyles.Top | AnchorStyles.Right;
buttonDelete.BackColor = SystemColors.ButtonHighlight;
buttonDelete.BorderColor = Color.Transparent;
buttonDelete.BorderRadius = 2;
buttonDelete.FlatStyle = FlatStyle.Flat;
buttonDelete.ForeColor = SystemColors.ControlText;
buttonDelete.Location = new Point(4, 182);
buttonDelete.Margin = new Padding(2, 4, 2, 4);
buttonDelete.Name = "buttonDelete";
buttonDelete.Secondary = false;
buttonDelete.Size = new Size(103, 25);
buttonDelete.TabIndex = 62;
buttonDelete.Text = "Delete";
buttonDelete.UseVisualStyleBackColor = false;
//
// AutoTDPGameProfileUI
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(386, 217);
Controls.Add(buttonDelete);
Controls.Add(buttonSave);
Controls.Add(panelLightingContent);
Controls.Add(panelPerformanceHeader);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
MinimizeBox = false;
Name = "AutoTDPGameProfileUI";
ShowIcon = false;
ShowInTaskbar = false;
Text = "Game Profile";
panelPerformanceHeader.ResumeLayout(false);
panelPerformanceHeader.PerformLayout();
((System.ComponentModel.ISupportInitialize)pictureKeyboard).EndInit();
panelLightingContent.ResumeLayout(false);
panelLightingContent.PerformLayout();
((System.ComponentModel.ISupportInitialize)numericUpDownFPS).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Panel panelPerformanceHeader;
private PictureBox pictureKeyboard;
private Label labelSettings;
private CheckBox checkBoxEnabled;
private Panel panelLightingContent;
private Label labelFPSSource;
private Label labelLimiter;
private TextBox textBoxTitle;
private TextBox textBoxProcessName;
private UI.RButton buttonSave;
private Label label2;
private Label label3;
private Label label1;
private UI.Slider sliderMinTDP;
private UI.Slider sliderMaxTDP;
private NumericUpDown numericUpDownFPS;
private Label labelMinTDP;
private Label labelMaxTDP;
private UI.RButton buttonDelete;
}
}

View File

@@ -0,0 +1,90 @@
using GHelper.UI;
namespace GHelper.AutoTDP
{
public partial class AutoTDPGameProfileUI : RForm
{
public GameProfile GameProfile;
private AutoTDPUI AutoTDPUI;
public AutoTDPGameProfileUI(GameProfile profile, AutoTDPUI parent)
{
AutoTDPUI = parent;
GameProfile = profile;
InitializeComponent();
sliderMinTDP.ValueChanged += SliderMinTDP_ValueChanged;
sliderMaxTDP.ValueChanged += SliderMaxTDP_ValueChanged;
buttonSave.Click += ButtonSave_Click;
buttonDelete.Click += ButtonDelete_Click;
InitTheme();
Shown += AutoTDPGameProfileUI_Shown;
VisualizeGameProfile();
}
private void ButtonDelete_Click(object? sender, EventArgs e)
{
AutoTDPUI.DeleteGameProfile(GameProfile);
Close();
}
private void ButtonSave_Click(object? sender, EventArgs e)
{
GameProfile.Enabled = checkBoxEnabled.Checked;
GameProfile.GameTitle = textBoxTitle.Text;
GameProfile.TargetFPS = ((int)numericUpDownFPS.Value);
GameProfile.MinTdp = sliderMinTDP.Value;
GameProfile.MaxTdp = sliderMaxTDP.Value;
AutoTDPUI.UpdateGameProfile(GameProfile);
Close();
}
private void SliderMaxTDP_ValueChanged(object? sender, EventArgs e)
{
labelMaxTDP.Text = sliderMaxTDP.Value + "W";
if (sliderMaxTDP.Value < sliderMinTDP.Value)
{
sliderMinTDP.Value = sliderMaxTDP.Value;
}
}
private void SliderMinTDP_ValueChanged(object? sender, EventArgs e)
{
labelMinTDP.Text = sliderMinTDP.Value + "W";
if (sliderMaxTDP.Value > sliderMinTDP.Value)
{
sliderMaxTDP.Value = sliderMinTDP.Value;
}
}
private void AutoTDPGameProfileUI_Shown(object? sender, EventArgs e)
{
if (Height > Program.settingsForm.Height)
{
Top = Program.settingsForm.Top + Program.settingsForm.Height - Height;
}
else
{
Top = Program.settingsForm.Top + 60;
}
Left = Program.settingsForm.Left - Width - ((AutoTDPUI.Width - Width) / 2);
}
private void VisualizeGameProfile()
{
sliderMinTDP.Value = GameProfile.MinTdp;
sliderMaxTDP.Value = GameProfile.MaxTdp;
numericUpDownFPS.Value = GameProfile.TargetFPS;
textBoxProcessName.Text = GameProfile.ProcessName;
textBoxTitle.Text = GameProfile.GameTitle;
checkBoxEnabled.Checked = GameProfile.Enabled;
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,435 @@
using System.Text.Json;
using GHelper.AutoTDP.FramerateSource;
using GHelper.AutoTDP.PowerLimiter;
namespace GHelper.AutoTDP
{
internal class AutoTDPService : IDisposable
{
private static readonly int INTERVAL_APP_CHECK = 5_000;
private static readonly int INTERVAL_FPS_CHECK = 2_500;
string GameProfileFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\GHelper\\AutoTDP.json";
IFramerateSource? framerateSouce;
IPowerLimiter? powerLimiter;
public List<GameProfile> GameProfiles = new List<GameProfile>();
private bool Running = false;
private Thread? checkerThread;
private Thread? tdpThread;
private double GameFPSPrevious = double.NaN;
private double GameFPS;
private int FramerateTargetReachedCounter;
private int FramerateDipCounter;
private static readonly int FPSDipHistorySize = 8;
private List<double> FramerateLog = new List<double>();
private double CurrentTDP;
private GameInstance? currentGame;
public AutoTDPService()
{
LoadGameProfiles();
Start();
}
/// <summary>
/// Whether the system is enabled and currently running.
/// </summary>
/// <returns></returns>
public bool IsRunning()
{
return Running;
}
/// <summary>
/// Whether a supported game is actively monitored and TDP is adjusted
/// </summary>
/// <returns></returns>
public bool IsActive()
{
return currentGame is not null;
}
public void Start()
{
if (!IsEnabled() || IsRunning())
{
return;
}
Running = true;
InitFramerateSource();
InitLimiter();
checkerThread = new Thread(() =>
{
while (Running)
{
CheckForGame();
try
{
Thread.Sleep(INTERVAL_APP_CHECK);
}
catch (ThreadInterruptedException)
{
continue;
}
}
});
checkerThread.Start();
}
public bool IsEnabled()
{
return AppConfig.Get("auto_tdp_enabled", 0) == 1;
}
public void InitFramerateSource()
{
string? source = AppConfig.GetString("auto_tdp_fps_source");
if ((source is null || source.Equals("rtss")) && RTSSFramerateSource.IsAvailable())
{
RTSSFramerateSource rtss = new RTSSFramerateSource();
RTSSFramerateSource.Start();
framerateSouce = rtss;
return;
}
}
public void InitLimiter()
{
string? limiter = AppConfig.GetString("auto_tdp_limiter");
if (limiter is null || limiter.Equals("asus_acpi"))
{
powerLimiter = new ASUSACPIPowerLimiter();
return;
}
if (limiter is not null && limiter.Equals("intel_msr"))
{
powerLimiter = new IntelMSRPowerLimiter();
return;
}
}
public void SaveGameProfiles()
{
string json = JsonSerializer.Serialize(GameProfiles);
File.WriteAllText(GameProfileFile, json);
}
public void LoadGameProfiles()
{
if (!File.Exists(GameProfileFile))
{
return;
}
string? json = File.ReadAllText(GameProfileFile);
if (json == null)
{
return;
}
try
{
GameProfiles = JsonSerializer.Deserialize<List<GameProfile>>(json);
}
catch (Exception e)
{
Logger.WriteLine("[AutoTDPService] Deserialization failed. Creating empty list. Message: " + e.Message);
GameProfiles = new List<GameProfile>();
}
}
public void CheckForGame()
{
if (currentGame is not null)
{
//Already handling a running game. No need to check for other games
return;
}
List<GameInstance> runningGames = framerateSouce.GetRunningGames();
if (runningGames.Count == 0)
{
Logger.WriteLine("[AutoTDPService] No games detected");
return;
}
foreach (GameInstance gi in runningGames)
{
Logger.WriteLine("[AutoTDPService] Detected App: " + gi.ProcessName + " PID: " + gi.ProcessID);
if (IsGameInList(gi.ProcessName))
{
Logger.WriteLine("[AutoTDPService] Detected Supported Game: " + gi.ProcessName + " PID: " + gi.ProcessID);
HandleGame(gi);
return;
}
}
}
public GameProfile? ProfileForGame(String? processName)
{
if (processName is null)
{
return null;
}
foreach (GameProfile gp in GameProfiles)
{
if (gp.ProcessName is not null && processName.EndsWith(gp.ProcessName))
{
return gp;
}
}
return null;
}
public bool IsGameInList(String? processName)
{
return ProfileForGame(processName) is not null;
}
public void HandleGame(GameInstance instance)
{
if (currentGame is not null)
{
Logger.WriteLine("[AutoTDPService] Already handling a game");
return;
}
if (tdpThread is not null)
{
tdpThread.Join();
tdpThread = null;
}
currentGame = instance;
StartGameHandler(instance);
}
public void Reset()
{
currentGame = null;
GameFPSPrevious = double.NaN;
GameFPS = 0;
if (powerLimiter is not null)
{
powerLimiter.ResetPowerLimits();
CurrentTDP = powerLimiter.GetCPUPowerLimit();
}
}
public void StartGameHandler(GameInstance instance)
{
GameProfile? profile = ProfileForGame(instance.ProcessName);
if (profile is null || powerLimiter is null || framerateSouce is null)
{
return;
}
tdpThread = new Thread(() =>
{
CurrentTDP = powerLimiter.GetCPUPowerLimit();
while (currentGame is not null && Running)
{
GameFPS = framerateSouce.GetFramerate(profile.ProcessName);
Logger.WriteLine("[AutoTDPService] (" + instance.ProcessName + ") Framerate " + GameFPS);
if (GameFPS < 0.0d)
{
//Game is not running anymore or RTSS lost its hook
Reset();
return;
}
AdjustPowerLimit(profile);
try
{
Thread.Sleep(INTERVAL_FPS_CHECK);
}
catch (ThreadInterruptedException)
{
continue;
}
}
});
tdpThread.Start();
}
private double FPSDipCorrection(double currentFramerate, double targetFPS)
{
double correction = 0.0d;
FramerateLog.Insert(0, currentFramerate);
//Remove last entry when exceeding the desired size.
if (FramerateLog.Count > FPSDipHistorySize)
{
FramerateLog.RemoveAt(FramerateLog.Count - 1);
}
if (targetFPS - 1 <= currentFramerate && currentFramerate <= targetFPS + 1)
{
FramerateTargetReachedCounter++;
if (FramerateTargetReachedCounter >= 4
&& FramerateTargetReachedCounter < FPSDipHistorySize
&& targetFPS - 0.75 <= FramerateLog.Take(4).Average()
&& FramerateLog.Take(3).Average() <= targetFPS + 0.15)
{
//short dip
FramerateDipCounter++;
correction = targetFPS + 0.75 - currentFramerate;
}
else if (FramerateDipCounter >= 5
&& targetFPS - 0.75 <= FramerateLog.Average()
&& FramerateLog.Average() <= targetFPS + 0.15)
{
//long dip
correction = targetFPS + 1.5 - currentFramerate;
FramerateTargetReachedCounter = FPSDipHistorySize;
}
}
else
{
//No dip, no correction
correction = 0.0;
FramerateTargetReachedCounter = 0;
FramerateDipCounter = 0;
}
return correction;
}
private double TDPDamper(double currentFramerate)
{
if (double.IsNaN(GameFPSPrevious)) GameFPSPrevious = currentFramerate;
double dF = -0.1d;
// Calculation
double deltaError = currentFramerate - GameFPSPrevious;
double dT = deltaError / (1010.0 / 1000.0);
double damping = CurrentTDP / currentFramerate * dF * dT;
GameFPSPrevious = currentFramerate;
return damping;
}
public void AdjustPowerLimit(GameProfile profile)
{
if (powerLimiter is null)
{
//Should not happen... but we also don't want it to crash
return;
}
double newPL = CurrentTDP;
Logger.WriteLine("[AutoTDPService] Current: " + (int)GameFPS + "FPS");
double delta = profile.TargetFPS - GameFPS - FPSDipCorrection(GameFPS, profile.TargetFPS);
delta = Math.Clamp(delta, -15, 15);
double adjustment = (delta * CurrentTDP / GameFPS) * 0.85;
//Dampen the changes to not change TDP too aggressively which would cause performance issues
adjustment += TDPDamper(GameFPS);
newPL += adjustment;
//Respect the limits that the user chose
newPL = Math.Clamp(newPL, profile.MinTdp, profile.MaxTdp);
Logger.WriteLine("[AutoTDPService] Setting Power Limit from " + CurrentTDP + "W to " + newPL + "W, Delta:" + adjustment);
//We only limit to full watts, no fractions. In this case, we will cut off the fractional part
powerLimiter.SetCPUPowerLimit((int)newPL);
CurrentTDP = newPL;
}
public void StopGameHandler()
{
if (tdpThread is not null)
{
currentGame = null;
tdpThread.Join();
tdpThread = null;
}
}
public void Shutdown()
{
Running = false;
if (checkerThread is not null)
{
checkerThread.Interrupt();
checkerThread.Join();
}
if (tdpThread is not null)
{
tdpThread.Interrupt();
tdpThread.Join();
}
if (powerLimiter is not null)
{
powerLimiter.ResetPowerLimits();
powerLimiter.Dispose();
powerLimiter = null;
}
if (framerateSouce is not null)
{
framerateSouce = null;
}
//Kill RTSS instance if we started one
RTSSFramerateSource.Stop();
}
public void Dispose()
{
Shutdown();
}
}
}

311
app/AutoTDP/AutoTDPUI.Designer.cs generated Normal file
View File

@@ -0,0 +1,311 @@
using GHelper.UI;
namespace GHelper.AutoTDP
{
partial class AutoTDPUI
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
panelPerformanceHeader = new Panel();
pictureKeyboard = new PictureBox();
labelGlobalSettings = new Label();
checkBoxEnabled = new CheckBox();
panelLightingContent = new Panel();
comboBoxFPSSource = new RComboBox();
labelFPSSource = new Label();
comboBoxLimiter = new RComboBox();
labelLimiter = new Label();
buttonAddGame = new RButton();
panelGamesHeader = new Panel();
pictureBox1 = new PictureBox();
labelGames = new Label();
tableLayoutGames = new TableLayoutPanel();
buttonGameDummy = new RButton();
panelPerformanceHeader.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pictureKeyboard).BeginInit();
panelLightingContent.SuspendLayout();
panelGamesHeader.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit();
tableLayoutGames.SuspendLayout();
SuspendLayout();
//
// panelPerformanceHeader
//
panelPerformanceHeader.BackColor = SystemColors.ControlLight;
panelPerformanceHeader.Controls.Add(pictureKeyboard);
panelPerformanceHeader.Controls.Add(labelGlobalSettings);
panelPerformanceHeader.Controls.Add(checkBoxEnabled);
panelPerformanceHeader.Dock = DockStyle.Top;
panelPerformanceHeader.Location = new Point(0, 0);
panelPerformanceHeader.Margin = new Padding(2);
panelPerformanceHeader.Name = "panelPerformanceHeader";
panelPerformanceHeader.Size = new Size(446, 30);
panelPerformanceHeader.TabIndex = 52;
//
// pictureKeyboard
//
pictureKeyboard.BackgroundImage = Properties.Resources.icons8_automation_32;
pictureKeyboard.BackgroundImageLayout = ImageLayout.Zoom;
pictureKeyboard.Location = new Point(3, 8);
pictureKeyboard.Margin = new Padding(2);
pictureKeyboard.Name = "pictureKeyboard";
pictureKeyboard.Size = new Size(16, 16);
pictureKeyboard.TabIndex = 35;
pictureKeyboard.TabStop = false;
//
// labelGlobalSettings
//
labelGlobalSettings.AutoSize = true;
labelGlobalSettings.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point);
labelGlobalSettings.Location = new Point(22, 8);
labelGlobalSettings.Margin = new Padding(4, 0, 4, 0);
labelGlobalSettings.Name = "labelGlobalSettings";
labelGlobalSettings.Size = new Size(100, 15);
labelGlobalSettings.TabIndex = 34;
labelGlobalSettings.Text = "General Settings";
//
// checkBoxEnabled
//
checkBoxEnabled.Location = new Point(244, 4);
checkBoxEnabled.Margin = new Padding(4, 0, 4, 0);
checkBoxEnabled.Name = "checkBoxEnabled";
checkBoxEnabled.Size = new Size(198, 25);
checkBoxEnabled.TabIndex = 53;
checkBoxEnabled.Text = "Enabled";
checkBoxEnabled.UseVisualStyleBackColor = true;
//
// panelLightingContent
//
panelLightingContent.AutoSize = true;
panelLightingContent.Controls.Add(comboBoxFPSSource);
panelLightingContent.Controls.Add(labelFPSSource);
panelLightingContent.Controls.Add(comboBoxLimiter);
panelLightingContent.Controls.Add(labelLimiter);
panelLightingContent.Dock = DockStyle.Top;
panelLightingContent.Location = new Point(0, 30);
panelLightingContent.Margin = new Padding(2);
panelLightingContent.Name = "panelLightingContent";
panelLightingContent.Padding = new Padding(0, 0, 0, 7);
panelLightingContent.Size = new Size(446, 67);
panelLightingContent.TabIndex = 56;
//
// comboBoxFPSSource
//
comboBoxFPSSource.BorderColor = Color.White;
comboBoxFPSSource.ButtonColor = Color.FromArgb(255, 255, 255);
comboBoxFPSSource.DropDownStyle = ComboBoxStyle.DropDownList;
comboBoxFPSSource.FlatStyle = FlatStyle.Flat;
comboBoxFPSSource.FormattingEnabled = true;
comboBoxFPSSource.Location = new Point(244, 37);
comboBoxFPSSource.Margin = new Padding(11, 0, 11, 0);
comboBoxFPSSource.Name = "comboBoxFPSSource";
comboBoxFPSSource.Size = new Size(191, 23);
comboBoxFPSSource.TabIndex = 56;
//
// labelFPSSource
//
labelFPSSource.Location = new Point(4, 37);
labelFPSSource.Margin = new Padding(4, 0, 4, 0);
labelFPSSource.Name = "labelFPSSource";
labelFPSSource.Size = new Size(211, 22);
labelFPSSource.TabIndex = 57;
labelFPSSource.Text = "FPS Source";
//
// comboBoxLimiter
//
comboBoxLimiter.BorderColor = Color.White;
comboBoxLimiter.ButtonColor = Color.FromArgb(255, 255, 255);
comboBoxLimiter.DropDownStyle = ComboBoxStyle.DropDownList;
comboBoxLimiter.FlatStyle = FlatStyle.Flat;
comboBoxLimiter.FormattingEnabled = true;
comboBoxLimiter.Location = new Point(244, 7);
comboBoxLimiter.Margin = new Padding(11, 0, 11, 0);
comboBoxLimiter.Name = "comboBoxLimiter";
comboBoxLimiter.Size = new Size(191, 23);
comboBoxLimiter.TabIndex = 46;
//
// labelLimiter
//
labelLimiter.Location = new Point(4, 7);
labelLimiter.Margin = new Padding(4, 0, 4, 0);
labelLimiter.Name = "labelLimiter";
labelLimiter.Size = new Size(211, 22);
labelLimiter.TabIndex = 47;
labelLimiter.Text = "Power Limiter";
//
// buttonAddGame
//
buttonAddGame.AccessibleName = "Keyboard Color";
buttonAddGame.Activated = false;
buttonAddGame.Anchor = AnchorStyles.Top | AnchorStyles.Right;
buttonAddGame.BackColor = SystemColors.ButtonHighlight;
buttonAddGame.BorderColor = Color.Transparent;
buttonAddGame.BorderRadius = 2;
buttonAddGame.FlatStyle = FlatStyle.Flat;
buttonAddGame.ForeColor = SystemColors.ControlText;
buttonAddGame.Location = new Point(339, 3);
buttonAddGame.Margin = new Padding(2, 4, 2, 4);
buttonAddGame.Name = "buttonAddGame";
buttonAddGame.Secondary = false;
buttonAddGame.Size = new Size(103, 25);
buttonAddGame.TabIndex = 60;
buttonAddGame.Text = "Add Game";
buttonAddGame.UseVisualStyleBackColor = false;
//
// panelGamesHeader
//
panelGamesHeader.BackColor = SystemColors.ControlLight;
panelGamesHeader.Controls.Add(pictureBox1);
panelGamesHeader.Controls.Add(buttonAddGame);
panelGamesHeader.Controls.Add(labelGames);
panelGamesHeader.Dock = DockStyle.Top;
panelGamesHeader.Location = new Point(0, 97);
panelGamesHeader.Margin = new Padding(2);
panelGamesHeader.Name = "panelGamesHeader";
panelGamesHeader.Size = new Size(446, 30);
panelGamesHeader.TabIndex = 61;
//
// pictureBox1
//
pictureBox1.BackgroundImage = Properties.Resources.icons8_software_32_white;
pictureBox1.BackgroundImageLayout = ImageLayout.Zoom;
pictureBox1.Location = new Point(3, 8);
pictureBox1.Margin = new Padding(2);
pictureBox1.Name = "pictureBox1";
pictureBox1.Size = new Size(16, 16);
pictureBox1.TabIndex = 35;
pictureBox1.TabStop = false;
//
// labelGames
//
labelGames.AutoSize = true;
labelGames.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point);
labelGames.Location = new Point(22, 8);
labelGames.Margin = new Padding(4, 0, 4, 0);
labelGames.Name = "labelGames";
labelGames.Size = new Size(45, 15);
labelGames.TabIndex = 34;
labelGames.Text = "Games";
//
// tableLayoutGames
//
tableLayoutGames.AutoSize = true;
tableLayoutGames.AutoSizeMode = AutoSizeMode.GrowAndShrink;
tableLayoutGames.ColumnCount = 4;
tableLayoutGames.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 25F));
tableLayoutGames.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 25F));
tableLayoutGames.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 25F));
tableLayoutGames.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 25F));
tableLayoutGames.Controls.Add(buttonGameDummy, 0, 0);
tableLayoutGames.Dock = DockStyle.Top;
tableLayoutGames.Location = new Point(0, 127);
tableLayoutGames.Margin = new Padding(4, 2, 4, 2);
tableLayoutGames.Name = "tableLayoutGames";
tableLayoutGames.RowCount = 7;
tableLayoutGames.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F));
tableLayoutGames.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F));
tableLayoutGames.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F));
tableLayoutGames.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F));
tableLayoutGames.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F));
tableLayoutGames.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F));
tableLayoutGames.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F));
tableLayoutGames.Size = new Size(446, 420);
tableLayoutGames.TabIndex = 62;
//
// buttonGameDummy
//
buttonGameDummy.AccessibleName = "DPI Setting 4";
buttonGameDummy.Activated = false;
buttonGameDummy.AutoSize = true;
buttonGameDummy.AutoSizeMode = AutoSizeMode.GrowAndShrink;
buttonGameDummy.BackColor = SystemColors.ControlLightLight;
buttonGameDummy.BorderColor = Color.LightGreen;
buttonGameDummy.BorderRadius = 5;
buttonGameDummy.Dock = DockStyle.Fill;
buttonGameDummy.FlatAppearance.BorderSize = 0;
buttonGameDummy.FlatStyle = FlatStyle.Flat;
buttonGameDummy.ForeColor = SystemColors.ControlText;
buttonGameDummy.ImageAlign = ContentAlignment.BottomCenter;
buttonGameDummy.Location = new Point(2, 2);
buttonGameDummy.Margin = new Padding(2);
buttonGameDummy.Name = "buttonGameDummy";
buttonGameDummy.Secondary = false;
buttonGameDummy.Size = new Size(107, 56);
buttonGameDummy.TabIndex = 7;
buttonGameDummy.Text = "Genshin Impact\r\n60FPS\r\n";
buttonGameDummy.TextImageRelation = TextImageRelation.ImageAboveText;
buttonGameDummy.UseVisualStyleBackColor = false;
buttonGameDummy.Visible = false;
//
// AutoTDPUI
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(446, 547);
Controls.Add(tableLayoutGames);
Controls.Add(panelGamesHeader);
Controls.Add(panelLightingContent);
Controls.Add(panelPerformanceHeader);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
MinimizeBox = false;
Name = "AutoTDPUI";
ShowIcon = false;
Text = "Auto TDP Settings";
panelPerformanceHeader.ResumeLayout(false);
panelPerformanceHeader.PerformLayout();
((System.ComponentModel.ISupportInitialize)pictureKeyboard).EndInit();
panelLightingContent.ResumeLayout(false);
panelGamesHeader.ResumeLayout(false);
panelGamesHeader.PerformLayout();
((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit();
tableLayoutGames.ResumeLayout(false);
tableLayoutGames.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Panel panelPerformanceHeader;
private PictureBox pictureKeyboard;
private Label labelGlobalSettings;
private Panel panelLightingContent;
private TableLayoutPanel tableLayoutGames;
private UI.RButton rButton1;
private CheckBox checkBoxEnabled;
private UI.RComboBox comboBoxLightingMode;
private Label labelLimiter;
private UI.RComboBox comboBoxFPSSource;
private Label labelFPSSource;
private RComboBox comboBoxLimiter;
private RButton buttonAddGame;
private Panel panelGamesHeader;
private PictureBox pictureBox1;
private Label labelGames;
private RButton buttonGameDummy;
}
}

222
app/AutoTDP/AutoTDPUI.cs Normal file
View File

@@ -0,0 +1,222 @@
using GHelper.AutoTDP.FramerateSource;
using GHelper.UI;
using Ryzen;
namespace GHelper.AutoTDP
{
public partial class AutoTDPUI : RForm
{
private AutoTDPGameProfileUI? profileUI;
public AutoTDPUI()
{
InitializeComponent();
InitTheme();
checkBoxEnabled.CheckedChanged += CheckBoxEnabled_CheckedChanged;
buttonAddGame.Click += ButtonAddGame_Click;
comboBoxLimiter.DropDownClosed += ComboBoxLimiter_DropDownClosed;
comboBoxFPSSource.DropDownClosed += ComboBoxFPSSource_DropDownClosed;
Shown += AutoTDPUI_Shown;
VisualizeGeneralSettings();
VizualizeGameList();
}
private void ComboBoxFPSSource_DropDownClosed(object? sender, EventArgs e)
{
if ((comboBoxFPSSource.SelectedItem as string).StartsWith("Riva"))
{
AppConfig.Set("auto_tdp_fps_source", "rtss");
}
}
private void ComboBoxLimiter_DropDownClosed(object? sender, EventArgs e)
{
if ((comboBoxLimiter.SelectedItem as string).StartsWith("Intel"))
{
AppConfig.Set("auto_tdp_limiter", "intel_msr");
}
if ((comboBoxLimiter.SelectedItem as string).StartsWith("ASUS ACPI"))
{
AppConfig.Set("auto_tdp_limiter", "asus_acpi");
}
}
private void CheckBoxEnabled_CheckedChanged(object? sender, EventArgs e)
{
AppConfig.Set("auto_tdp_enabled", checkBoxEnabled.Checked ? 1 : 0);
if (Program.autoTDPService.IsEnabled())
{
Program.autoTDPService.Start();
}
else
{
Program.autoTDPService.Shutdown();
}
}
private void ButtonAddGame_Click(object? sender, EventArgs e)
{
string? path = null;
Thread t = new Thread(() =>
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Executables (*.exe)|*.exe";
if (ofd.ShowDialog() == DialogResult.OK)
{
path = ofd.FileName;
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
if (path is null)
{
//User did not select a file
return;
}
GameProfile p = new GameProfile();
p.ProcessName = Path.GetFileName(path);
p.GameTitle = Path.GetFileName(path);
p.Enabled = true;
p.TargetFPS = 60;
p.MaxTdp = 40;
p.MinTdp = 20;
profileUI = new AutoTDPGameProfileUI(p, this);
profileUI.TopMost = true;
profileUI.FormClosed += ProfileUI_FormClosed;
profileUI.Show();
}
private void ProfileUI_FormClosed(object? sender, FormClosedEventArgs e)
{
profileUI = null;
}
private void AutoTDPUI_Shown(object? sender, EventArgs e)
{
if (Height > Program.settingsForm.Height)
{
Top = Program.settingsForm.Top + Program.settingsForm.Height - Height;
}
else
{
Top = Program.settingsForm.Top;
}
Left = Program.settingsForm.Left - Width - 5;
}
private void VisualizeGeneralSettings()
{
checkBoxEnabled.Checked = AppConfig.Get("auto_tdp_enabled", 0) == 1;
if (!RyzenControl.IsAMD())
comboBoxLimiter.Items.Add("Intel MSR Power Limiter");
comboBoxLimiter.Items.Add("ASUS ACPI Power Limiter");
string? limiter = AppConfig.GetString("auto_tdp_limiter");
if (comboBoxLimiter.Items.Count > 0 && limiter is null)
comboBoxLimiter.SelectedIndex = 0;
if (!RyzenControl.IsAMD() && limiter is not null && limiter.Equals("intel_msr"))
{
comboBoxLimiter.SelectedIndex = 0;
}
if (limiter is not null && limiter.Equals("asus_acpi"))
{
comboBoxLimiter.SelectedIndex = !RyzenControl.IsAMD() ? 1 : 0;
}
if (RTSSFramerateSource.IsAvailable())
comboBoxFPSSource.Items.Add("Riva Tuner Statistics Server");
string? source = AppConfig.GetString("auto_tdp_fps_source", null);
if (comboBoxFPSSource.Items.Count > 0 && source is null)
comboBoxFPSSource.SelectedIndex = 0;
if (source is not null && source.Equals("rtss"))
{
comboBoxFPSSource.SelectedIndex = 0;
}
}
private void VizualizeGameList()
{
//Due to my lousy skills in UI design, the game table is limited to 7x4 games.
buttonAddGame.Enabled = Program.autoTDPService.GameProfiles.Count < 7 * 4;
tableLayoutGames.Controls.Clear();
foreach (GameProfile gp in Program.autoTDPService.GameProfiles)
{
RButton bt = new RButton();
bt.Text = gp.GameTitle + "\n" + gp.TargetFPS + " FPS";
bt.Dock = DockStyle.Fill;
bt.FlatStyle = FlatStyle.Flat;
bt.FlatAppearance.BorderColor = RForm.borderMain;
bt.UseVisualStyleBackColor = false;
bt.AutoSize = true;
bt.AutoSizeMode = AutoSizeMode.GrowAndShrink;
bt.BackColor = RForm.buttonMain;
bt.ForeColor = RForm.foreMain;
bt.Click += Bt_Click;
bt.Tag = gp;
tableLayoutGames.Controls.Add(bt);
}
}
private void Bt_Click(object? sender, EventArgs e)
{
GameProfile gp = (GameProfile)((RButton)sender).Tag;
profileUI = new AutoTDPGameProfileUI(gp, this);
profileUI.TopMost = true;
profileUI.FormClosed += ProfileUI_FormClosed;
profileUI.Show();
}
public void DeleteGameProfile(GameProfile gp)
{
if (Program.autoTDPService.IsGameInList(gp.ProcessName))
{
Program.autoTDPService.GameProfiles.Remove(gp);
}
Program.autoTDPService.SaveGameProfiles();
VizualizeGameList();
}
public void UpdateGameProfile(GameProfile gp)
{
if (!Program.autoTDPService.IsGameInList(gp.ProcessName))
{
Program.autoTDPService.GameProfiles.Add(gp);
}
Program.autoTDPService.SaveGameProfiles();
VizualizeGameList();
}
}
}

120
app/AutoTDP/AutoTDPUI.resx Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,17 @@
namespace GHelper.AutoTDP.FramerateSource
{
internal class GameInstance
{
public string? ProcessName { get; set; }
public int ProcessID { get; set; }
}
internal interface IFramerateSource
{
public double GetFramerate(string processName);
public List<GameInstance> GetRunningGames();
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RTSSSharedMemoryNET;
namespace GHelper.AutoTDP.FramerateSource
{
internal class RTSSFramerateSource : IFramerateSource
{
private static Process? rtssInstance;
private static OSD? osd;
public static string RTSSPath { get; set; }
public static bool IsRunning => Process.GetProcessesByName("RTSS").Length != 0;
static RTSSFramerateSource()
{
RTSSPath = @"C:\Program Files (x86)\RivaTuner Statistics Server\RTSS.exe";
}
public static bool IsAvailable()
{
return File.Exists(RTSSPath);
}
public static void Start()
{
if ((rtssInstance == null || rtssInstance.HasExited) && !IsRunning && File.Exists(RTSSPath))
{
try
{
rtssInstance = Process.Start(RTSSPath);
Thread.Sleep(2000); // If it works, don't touch it
}
catch (Exception exc)
{
Logger.WriteLine("Could not start RTSS Service" + exc.Message);
}
RunOSD();
}
else
{
RunOSD();
}
}
public List<GameInstance> GetRunningGames()
{
if (!IsRunning)
{
return new List<GameInstance>();
}
List<GameInstance> giL = new List<GameInstance>();
foreach (AppEntry appEntry in OSD.GetAppEntries())
{
GameInstance i = new GameInstance();
i.ProcessID = appEntry.ProcessId;
i.ProcessName = appEntry.Name;
giL.Add(i);
}
return giL;
}
public double GetFramerate(string processName)
{
if (!IsRunning)
{
return -1.0d;
}
try
{
var appE = OSD.GetAppEntries()
.Where(x => (x.Flags & AppFlags.MASK) != AppFlags.None).FirstOrDefault(a => a.Name.EndsWith(processName));
if (appE is null)
return -1.0d;
return (double)appE.StatFrameTimeBufFramerate / 10;
}
catch (InvalidDataException)
{
}
catch (FileNotFoundException)
{
}
return -1.0d;
}
public static void RunOSD()
{
if (osd == null)
{
try
{
osd = new OSD("GHELPER");
}
catch (Exception exc)
{
Logger.WriteLine("Could not start OSD" + exc.Message);
}
}
}
public static void Stop()
{
if (rtssInstance != null && !rtssInstance.HasExited)
{
try
{
rtssInstance.Kill();
rtssInstance = null;
var proc = Process.GetProcessesByName("RTSSHooksLoader64");
proc[0].Kill();
}
catch (Exception)
{
// Ignored
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GHelper.AutoTDP
{
public class GameProfile
{
public string GameTitle { get; set; }
public string ProcessName { get; set; }
public int TargetFPS { get; set; }
public int MinTdp { get; set; }
public int MaxTdp { get; set; }
public bool Enabled { get; set; }
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GHelper.AutoTDP.PowerLimiter
{
internal class ASUSACPIPowerLimiter : IPowerLimiter
{
private int DefaultA0 = Program.acpi.DeviceGet(AsusACPI.PPT_APUA0);
private int DefaultA3 = Program.acpi.DeviceGet(AsusACPI.PPT_APUA0);
public void SetCPUPowerLimit(int watts)
{
if (Program.acpi.DeviceGet(AsusACPI.PPT_APUA0) >= 0)
{
Program.acpi.DeviceSet(AsusACPI.PPT_APUA3, watts, "PowerLimit A3");
Program.acpi.DeviceSet(AsusACPI.PPT_APUA0, watts, "PowerLimit A0");
}
}
public int GetCPUPowerLimit()
{
return Program.acpi.DeviceGet(AsusACPI.PPT_APUA0);
}
public void Dispose()
{
//Nothing to dispose here
}
public void ResetPowerLimits()
{
//Load limits that were set before the limiter engaged
Program.acpi.DeviceSet(AsusACPI.PPT_APUA3, DefaultA0, "PowerLimit A3");
Program.acpi.DeviceSet(AsusACPI.PPT_APUA0, DefaultA3, "PowerLimit A0");
}
}
}

View File

@@ -0,0 +1,12 @@
namespace GHelper.AutoTDP.PowerLimiter
{
internal interface IPowerLimiter : IDisposable
{
public void SetCPUPowerLimit(int watts);
public int GetCPUPowerLimit();
public void ResetPowerLimits();
}
}

View File

@@ -0,0 +1,106 @@
using HidSharp.Reports.Units;
using Ryzen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GHelper.AutoTDP.PowerLimiter
{
internal class IntelMSRPowerLimiter : IPowerLimiter
{
private Ols ols;
private uint DefaultEax = 0; // Set on first reading
private uint DefaultEdx = 0;
//Lower 14 bits are the power limits
private uint PL1_MASK = 0x3FFF;
private uint PL2_MASK = 0x3FFF;
//The power unit factor (Default is 0.125 for most Intel CPUs).
private double PowerUnit = 0x0;
public IntelMSRPowerLimiter()
{
ols = new Ols();
ols.InitializeOls();
ReadPowerUnit();
}
public void ReadPowerUnit()
{
uint eax = 0;
uint edx = 0;
ols.Rdmsr(0x606, ref eax, ref edx);
uint pwr = eax & 0x03;
PowerUnit = 1 / Math.Pow(2, pwr);
}
public void SetCPUPowerLimit(int watts)
{
uint eax = 0;
uint edx = 0;
ols.Rdmsr(0x610, ref eax, ref edx);
uint watsRapl = (uint)(watts / PowerUnit);
//Set limits for both PL1 and PL2
uint eaxFilterd = eax & ~PL1_MASK;
uint edxFilterd = edx & ~PL2_MASK;
eaxFilterd |= watsRapl;
edxFilterd |= watsRapl;
//Enable clamping
eaxFilterd |= 0x8000;
edxFilterd |= 0x8000;
ols.Wrmsr(0x610, eaxFilterd, edxFilterd);
}
public int GetCPUPowerLimit()
{
uint eax = 0;
uint edx = 0;
ols.Rdmsr(0x610, ref eax, ref edx);
if (DefaultEax == 0)
{
//Store default settings to reset them on exit
DefaultEax = eax;
DefaultEdx = edx;
}
uint pl1 = eax & PL1_MASK;
uint pl2 = edx & PL2_MASK;
return (int)(pl1 * PowerUnit);
}
public void ResetPowerLimits()
{
if (DefaultEax == 0)
{
return;
}
ols.Wrmsr(0x610, DefaultEax, DefaultEdx);
}
public void Dispose()
{
ols.DeinitializeOls();
ols.Dispose();
}
}
}