From 84a6fd4d5fcb40b90cc42d586adf91c2a45539f7 Mon Sep 17 00:00:00 2001 From: IceStormNG Date: Fri, 1 Mar 2024 07:47:00 +0100 Subject: [PATCH] Added AutoTDP feature with RTSS source and Intel + ASUS power limiters --- app/AutoTDP/AutoTDPGameProfileUI.Designer.cs | 337 ++++++++++++++ app/AutoTDP/AutoTDPGameProfileUI.cs | 90 ++++ app/AutoTDP/AutoTDPGameProfileUI.resx | 120 +++++ app/AutoTDP/AutoTDPService.cs | 435 ++++++++++++++++++ app/AutoTDP/AutoTDPUI.Designer.cs | 311 +++++++++++++ app/AutoTDP/AutoTDPUI.cs | 222 +++++++++ app/AutoTDP/AutoTDPUI.resx | 120 +++++ .../FramerateSource/IFramerateSource.cs | 17 + .../FramerateSource/RTSSFramerateSource.cs | 136 ++++++ app/AutoTDP/GameProfile.cs | 18 + .../PowerLimiter/ASUSACPIPowerLimiter.cs | 42 ++ app/AutoTDP/PowerLimiter/IPowerLimiter.cs | 12 + .../PowerLimiter/IntelMSRPowerLimiter.cs | 106 +++++ app/GHelper.csproj | 8 + app/GHelper.sln | 6 + app/Program.cs | 4 + app/RTSSSharedMemoryNET.dll | Bin 0 -> 75264 bytes app/Settings.Designer.cs | 47 ++ app/Settings.cs | 31 ++ 19 files changed, 2062 insertions(+) create mode 100644 app/AutoTDP/AutoTDPGameProfileUI.Designer.cs create mode 100644 app/AutoTDP/AutoTDPGameProfileUI.cs create mode 100644 app/AutoTDP/AutoTDPGameProfileUI.resx create mode 100644 app/AutoTDP/AutoTDPService.cs create mode 100644 app/AutoTDP/AutoTDPUI.Designer.cs create mode 100644 app/AutoTDP/AutoTDPUI.cs create mode 100644 app/AutoTDP/AutoTDPUI.resx create mode 100644 app/AutoTDP/FramerateSource/IFramerateSource.cs create mode 100644 app/AutoTDP/FramerateSource/RTSSFramerateSource.cs create mode 100644 app/AutoTDP/GameProfile.cs create mode 100644 app/AutoTDP/PowerLimiter/ASUSACPIPowerLimiter.cs create mode 100644 app/AutoTDP/PowerLimiter/IPowerLimiter.cs create mode 100644 app/AutoTDP/PowerLimiter/IntelMSRPowerLimiter.cs create mode 100644 app/RTSSSharedMemoryNET.dll diff --git a/app/AutoTDP/AutoTDPGameProfileUI.Designer.cs b/app/AutoTDP/AutoTDPGameProfileUI.Designer.cs new file mode 100644 index 00000000..66b9c654 --- /dev/null +++ b/app/AutoTDP/AutoTDPGameProfileUI.Designer.cs @@ -0,0 +1,337 @@ +namespace GHelper.AutoTDP +{ + partial class AutoTDPGameProfileUI + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/app/AutoTDP/AutoTDPGameProfileUI.cs b/app/AutoTDP/AutoTDPGameProfileUI.cs new file mode 100644 index 00000000..95f96677 --- /dev/null +++ b/app/AutoTDP/AutoTDPGameProfileUI.cs @@ -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; + } + } +} diff --git a/app/AutoTDP/AutoTDPGameProfileUI.resx b/app/AutoTDP/AutoTDPGameProfileUI.resx new file mode 100644 index 00000000..af32865e --- /dev/null +++ b/app/AutoTDP/AutoTDPGameProfileUI.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/app/AutoTDP/AutoTDPService.cs b/app/AutoTDP/AutoTDPService.cs new file mode 100644 index 00000000..dd9eb2be --- /dev/null +++ b/app/AutoTDP/AutoTDPService.cs @@ -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 GameProfiles = new List(); + + 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 FramerateLog = new List(); + + private double CurrentTDP; + + private GameInstance? currentGame; + + public AutoTDPService() + { + LoadGameProfiles(); + + Start(); + } + + /// + /// Whether the system is enabled and currently running. + /// + /// + public bool IsRunning() + { + return Running; + } + + /// + /// Whether a supported game is actively monitored and TDP is adjusted + /// + /// + 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>(json); + } + catch (Exception e) + { + Logger.WriteLine("[AutoTDPService] Deserialization failed. Creating empty list. Message: " + e.Message); + GameProfiles = new List(); + } + + } + + public void CheckForGame() + { + if (currentGame is not null) + { + //Already handling a running game. No need to check for other games + return; + } + List 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(); + } + } +} diff --git a/app/AutoTDP/AutoTDPUI.Designer.cs b/app/AutoTDP/AutoTDPUI.Designer.cs new file mode 100644 index 00000000..0b48a2b1 --- /dev/null +++ b/app/AutoTDP/AutoTDPUI.Designer.cs @@ -0,0 +1,311 @@ +using GHelper.UI; + +namespace GHelper.AutoTDP +{ + partial class AutoTDPUI + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/app/AutoTDP/AutoTDPUI.cs b/app/AutoTDP/AutoTDPUI.cs new file mode 100644 index 00000000..525cb299 --- /dev/null +++ b/app/AutoTDP/AutoTDPUI.cs @@ -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(); + } + } +} diff --git a/app/AutoTDP/AutoTDPUI.resx b/app/AutoTDP/AutoTDPUI.resx new file mode 100644 index 00000000..af32865e --- /dev/null +++ b/app/AutoTDP/AutoTDPUI.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/app/AutoTDP/FramerateSource/IFramerateSource.cs b/app/AutoTDP/FramerateSource/IFramerateSource.cs new file mode 100644 index 00000000..3bf7aa85 --- /dev/null +++ b/app/AutoTDP/FramerateSource/IFramerateSource.cs @@ -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 GetRunningGames(); + } +} diff --git a/app/AutoTDP/FramerateSource/RTSSFramerateSource.cs b/app/AutoTDP/FramerateSource/RTSSFramerateSource.cs new file mode 100644 index 00000000..e70ab6ac --- /dev/null +++ b/app/AutoTDP/FramerateSource/RTSSFramerateSource.cs @@ -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 GetRunningGames() + { + if (!IsRunning) + { + return new List(); + } + + List giL = new List(); + + 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 + } + } + } + } +} diff --git a/app/AutoTDP/GameProfile.cs b/app/AutoTDP/GameProfile.cs new file mode 100644 index 00000000..c0165592 --- /dev/null +++ b/app/AutoTDP/GameProfile.cs @@ -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; } + } +} diff --git a/app/AutoTDP/PowerLimiter/ASUSACPIPowerLimiter.cs b/app/AutoTDP/PowerLimiter/ASUSACPIPowerLimiter.cs new file mode 100644 index 00000000..4c5ab738 --- /dev/null +++ b/app/AutoTDP/PowerLimiter/ASUSACPIPowerLimiter.cs @@ -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"); + } + } +} diff --git a/app/AutoTDP/PowerLimiter/IPowerLimiter.cs b/app/AutoTDP/PowerLimiter/IPowerLimiter.cs new file mode 100644 index 00000000..05e01996 --- /dev/null +++ b/app/AutoTDP/PowerLimiter/IPowerLimiter.cs @@ -0,0 +1,12 @@ +namespace GHelper.AutoTDP.PowerLimiter +{ + internal interface IPowerLimiter : IDisposable + { + public void SetCPUPowerLimit(int watts); + + public int GetCPUPowerLimit(); + + public void ResetPowerLimits(); + + } +} diff --git a/app/AutoTDP/PowerLimiter/IntelMSRPowerLimiter.cs b/app/AutoTDP/PowerLimiter/IntelMSRPowerLimiter.cs new file mode 100644 index 00000000..5d93ae5b --- /dev/null +++ b/app/AutoTDP/PowerLimiter/IntelMSRPowerLimiter.cs @@ -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(); + } + } +} diff --git a/app/GHelper.csproj b/app/GHelper.csproj index 56011b39..656a5a4d 100644 --- a/app/GHelper.csproj +++ b/app/GHelper.csproj @@ -88,6 +88,14 @@ + + + RTSSSharedMemoryNET.dll + PreserveNewest + true + + + True diff --git a/app/GHelper.sln b/app/GHelper.sln index 0b4abb97..64effb6e 100644 --- a/app/GHelper.sln +++ b/app/GHelper.sln @@ -14,18 +14,24 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Debug|Any CPU.ActiveCfg = Debug|x64 {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Debug|Any CPU.Build.0 = Debug|x64 {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Debug|x64.ActiveCfg = Debug|Any CPU {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Debug|x64.Build.0 = Debug|Any CPU + {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Debug|x86.ActiveCfg = Debug|Any CPU + {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Debug|x86.Build.0 = Debug|Any CPU {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Release|Any CPU.ActiveCfg = Release|x64 {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Release|Any CPU.Build.0 = Release|x64 {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Release|x64.ActiveCfg = Release|x64 {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Release|x64.Build.0 = Release|x64 + {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Release|x86.ActiveCfg = Release|Any CPU + {D6138BB1-8FDB-4835-87EF-2FE41A3DD604}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/app/Program.cs b/app/Program.cs index 7cd18809..2f07fda8 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -1,4 +1,5 @@ using GHelper.Ally; +using GHelper.AutoTDP; using GHelper.Battery; using GHelper.Display; using GHelper.Gpu; @@ -46,6 +47,8 @@ namespace GHelper private static PowerLineStatus isPlugged = SystemInformation.PowerStatus.PowerLineStatus; + public static AutoTDPService autoTDPService = new AutoTDPService(); + // The main entry point for the application public static void Main(string[] args) { @@ -306,6 +309,7 @@ namespace GHelper static void OnExit(object sender, EventArgs e) { + autoTDPService.Shutdown(); trayIcon.Visible = false; PeripheralsProvider.UnregisterForDeviceEvents(); clamshellControl.UnregisterDisplayEvents(); diff --git a/app/RTSSSharedMemoryNET.dll b/app/RTSSSharedMemoryNET.dll new file mode 100644 index 0000000000000000000000000000000000000000..37197eaaf571ad4342952711ecbe7d556c2c2f8c GIT binary patch literal 75264 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 literal 0 HcmV?d00001 diff --git a/app/Settings.Designer.cs b/app/Settings.Designer.cs index e08037a2..6e2f9ed0 100644 --- a/app/Settings.Designer.cs +++ b/app/Settings.Designer.cs @@ -67,6 +67,8 @@ namespace GHelper labelCPUFan = new Label(); panelGPU = new Panel(); labelTipGPU = new Label(); + tableAdditionalGPUFeature = new TableLayoutPanel(); + buttonAutoTDP = new RButton(); tableAMD = new TableLayoutPanel(); buttonOverlay = new RButton(); buttonFPS = new RButton(); @@ -143,6 +145,7 @@ namespace GHelper panelCPUTitle.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)picturePerf).BeginInit(); panelGPU.SuspendLayout(); + tableAdditionalGPUFeature.SuspendLayout(); tableAMD.SuspendLayout(); tableGPU.SuspendLayout(); panelGPUTitle.SuspendLayout(); @@ -723,6 +726,7 @@ namespace GHelper panelGPU.AutoSize = true; panelGPU.AutoSizeMode = AutoSizeMode.GrowAndShrink; panelGPU.Controls.Add(labelTipGPU); + panelGPU.Controls.Add(tableAdditionalGPUFeature); panelGPU.Controls.Add(tableAMD); panelGPU.Controls.Add(tableGPU); panelGPU.Controls.Add(panelGPUTitle); @@ -745,6 +749,46 @@ namespace GHelper labelTipGPU.Size = new Size(787, 36); labelTipGPU.TabIndex = 20; // + // tableAdditionalGPUFeature + // + tableAdditionalGPUFeature.AutoSize = true; + tableAdditionalGPUFeature.AutoSizeMode = AutoSizeMode.GrowAndShrink; + tableAdditionalGPUFeature.ColumnCount = 3; + tableAdditionalGPUFeature.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33.3333321F)); + tableAdditionalGPUFeature.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33.3333321F)); + tableAdditionalGPUFeature.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33.3333321F)); + tableAdditionalGPUFeature.Controls.Add(buttonAutoTDP, 0, 0); + tableAdditionalGPUFeature.Dock = DockStyle.Top; + tableAdditionalGPUFeature.Location = new Point(10, 198); + tableAdditionalGPUFeature.Margin = new Padding(2, 1, 2, 1); + tableAdditionalGPUFeature.Name = "tableAdditionalGPUFeature"; + tableAdditionalGPUFeature.RowCount = 1; + tableAdditionalGPUFeature.RowStyles.Add(new RowStyle(SizeType.Absolute, 60F)); + tableAdditionalGPUFeature.Size = new Size(392, 60); + tableAdditionalGPUFeature.TabIndex = 25; + // + // buttonAutoTDP + // + buttonAutoTDP.Activated = false; + buttonAutoTDP.BackColor = SystemColors.ControlLightLight; + buttonAutoTDP.BorderColor = Color.Transparent; + buttonAutoTDP.BorderRadius = 5; + buttonAutoTDP.Dock = DockStyle.Fill; + buttonAutoTDP.FlatAppearance.BorderSize = 0; + buttonAutoTDP.FlatStyle = FlatStyle.Flat; + buttonAutoTDP.ForeColor = SystemColors.ControlText; + buttonAutoTDP.Image = Properties.Resources.icons8_processor_32; + buttonAutoTDP.ImageAlign = ContentAlignment.MiddleRight; + buttonAutoTDP.Location = new Point(1, 1); + buttonAutoTDP.Margin = new Padding(1, 1, 1, 1); + buttonAutoTDP.Name = "buttonAutoTDP"; + buttonAutoTDP.Secondary = false; + buttonAutoTDP.Size = new Size(128, 38); + buttonAutoTDP.TabIndex = 11; + buttonAutoTDP.Text = "Auto TDP"; + buttonAutoTDP.TextImageRelation = TextImageRelation.ImageBeforeText; + buttonAutoTDP.UseVisualStyleBackColor = false; + // // tableAMD // tableAMD.AutoSize = true; @@ -1790,6 +1834,7 @@ namespace GHelper ((System.ComponentModel.ISupportInitialize)picturePerf).EndInit(); panelGPU.ResumeLayout(false); panelGPU.PerformLayout(); + tableAdditionalGPUFeature.ResumeLayout(false); tableAMD.ResumeLayout(false); tableGPU.ResumeLayout(false); panelGPUTitle.ResumeLayout(false); @@ -1931,5 +1976,7 @@ namespace GHelper private Label labelGammaTitle; private CheckBox checkMatrixLid; private Panel panelMatrixAuto; + private TableLayoutPanel tableAdditionalGPUFeature; + private RButton buttonAutoTDP; } } diff --git a/app/Settings.cs b/app/Settings.cs index b5c34a81..3355764a 100644 --- a/app/Settings.cs +++ b/app/Settings.cs @@ -1,5 +1,6 @@ using GHelper.Ally; using GHelper.AnimeMatrix; +using GHelper.AutoTDP; using GHelper.AutoUpdate; using GHelper.Battery; using GHelper.Display; @@ -28,6 +29,7 @@ namespace GHelper AutoUpdateControl updateControl; AsusMouseSettings? mouseSettings; + AutoTDPUI? autoTdpUi; public AniMatrixControl matrixControl; @@ -244,6 +246,8 @@ namespace GHelper buttonFPS.Click += ButtonFPS_Click; buttonOverlay.Click += ButtonOverlay_Click; + buttonAutoTDP.Click += ButtonAutoTDP_Click; + Text = "G-Helper " + (ProcessHelper.IsUserAdministrator() ? "—" : "-") + " " + AppConfig.GetModelShort(); TopMost = AppConfig.Is("topmost"); @@ -261,6 +265,32 @@ namespace GHelper panelPerformance.Focus(); } + private void ButtonAutoTDP_Click(object? sender, EventArgs e) + { + autoTdpUi = new AutoTDPUI(); + autoTdpUi.TopMost = true; + autoTdpUi.FormClosed += AutoTdpUi_FormClosed; + autoTdpUi.Disposed += AutoTdpUi_Disposed; + if (!autoTdpUi.IsDisposed) + { + autoTdpUi.Show(); + } + else + { + autoTdpUi = null; + } + } + + private void AutoTdpUi_Disposed(object? sender, EventArgs e) + { + autoTdpUi = null; + } + + private void AutoTdpUi_FormClosed(object? sender, FormClosedEventArgs e) + { + autoTdpUi = null; + } + private void SliderGamma_ValueChanged(object? sender, EventArgs e) { screenControl.SetGamma(sliderGamma.Value); @@ -1081,6 +1111,7 @@ namespace GHelper private void ButtonQuit_Click(object? sender, EventArgs e) { + Program.autoTDPService.Shutdown(); matrixControl.Dispose(); Close(); Program.trayIcon.Visible = false;