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 00000000..37197eaa Binary files /dev/null and b/app/RTSSSharedMemoryNET.dll differ 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;