diff --git a/PowerControl/Controller.cs b/PowerControl/Controller.cs index ab42857..3446703 100644 --- a/PowerControl/Controller.cs +++ b/PowerControl/Controller.cs @@ -69,6 +69,34 @@ namespace PowerControl rootMenu.VisibleChanged += delegate { updateOSD(); }; contextMenu.Items.Add(new ToolStripSeparator()); + if (Settings.Default.EnableExperimentalFeatures) + { + var installEDIDItem = contextMenu.Items.Add("Install &Resolutions"); + installEDIDItem.Click += delegate { Helpers.AMD.EDID.SetEDID(Resources.CRU_SteamDeck); }; + var replaceEDIDItem = contextMenu.Items.Add("Replace &Resolutions"); + replaceEDIDItem.Click += delegate { Helpers.AMD.EDID.SetEDID(new byte[0]); Helpers.AMD.EDID.SetEDID(Resources.CRU_SteamDeck); }; + var uninstallEDIDItem = contextMenu.Items.Add("Revert &Resolutions"); + uninstallEDIDItem.Click += delegate { Helpers.AMD.EDID.SetEDID(new byte[0]); }; + contextMenu.Opening += delegate + { + if (ExternalHelpers.DisplayConfig.IsInternalConnected == true) + { + var edid = Helpers.AMD.EDID.GetEDID() ?? new byte[0]; + var edidInstalled = Resources.CRU_SteamDeck.SequenceEqual(edid); + installEDIDItem.Visible = edid.Length <= 128; + replaceEDIDItem.Visible = !edidInstalled && edid.Length > 128; + uninstallEDIDItem.Visible = edid.Length > 128; + } + else + { + installEDIDItem.Visible = false; + replaceEDIDItem.Visible = false; + uninstallEDIDItem.Visible = false; + } + }; + contextMenu.Items.Add(new ToolStripSeparator()); + } + if (startupManager.IsAvailable) { var startupItem = new ToolStripMenuItem("Run On Startup"); diff --git a/PowerControl/Helpers/AMD/ADL.cs b/PowerControl/Helpers/AMD/ADL.cs index 8f2b8a0..38f5db5 100644 --- a/PowerControl/Helpers/AMD/ADL.cs +++ b/PowerControl/Helpers/AMD/ADL.cs @@ -143,6 +143,30 @@ namespace PowerControl.Helpers.AMD } #endregion ADLDisplayInfo + [StructLayout(LayoutKind.Sequential)] + internal struct ADLDisplayEDIDData + { + internal int iSize; + internal int iFlag; + internal int iEDIDSize; + internal int iBlockIndex; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)ADL.ADL_MAX_EDIDDATA_SIZE)] + internal byte[] cEDIDData; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + internal int[] iReserved; + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct ADLDisplayEDIDDataX2 + { + internal int iSize; + internal int iFlag; + internal int iEDIDSize; + internal int iIgnored; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)2 * ADL.ADL_MAX_EDIDDATA_SIZE)] + internal byte[] cEDIDData; + }; + #region Radeon Image Sharpening [StructLayout(LayoutKind.Sequential)] internal struct ADL_RIS_SETTINGS @@ -192,6 +216,8 @@ namespace PowerControl.Helpers.AMD internal const int ADL_MAX_GLSYNC_PORT_LEDS = 8; /// Maximum number of ADLMOdes for the adapter internal const int ADL_MAX_NUM_DISPLAYMODES = 1024; + internal const int ADL_MAX_EDIDDATA_SIZE = 256; + internal const int ADL_MAX_EDID_EXTENSION_BLOCKS = 3; internal const int ADL_DISPLAY_DISPLAYINFO_DISPLAYCONNECTED = 0x00000001; @@ -265,6 +291,12 @@ namespace PowerControl.Helpers.AMD [DllImport(Atiadlxx_FileName)] internal static extern int ADL2_RIS_Settings_Set(IntPtr context, int adapterIndex, ADL_RIS_SETTINGS settings, ADL_RIS_NOTFICATION_REASON reason); + + [DllImport(Atiadlxx_FileName)] + internal static extern int ADL2_Display_EdidData_Get(IntPtr context, int adapterIndex, int displayIndex, ref ADLDisplayEDIDData edidData); + + [DllImport(Atiadlxx_FileName)] + internal static extern int ADL2_Display_EdidData_Set(IntPtr context, int adapterIndex, int displayIndex, ref ADLDisplayEDIDDataX2 edidData); #endregion DLLImport #region ADL_Main_Memory_Alloc diff --git a/PowerControl/Helpers/AMD/EDID.cs b/PowerControl/Helpers/AMD/EDID.cs new file mode 100644 index 0000000..095684a --- /dev/null +++ b/PowerControl/Helpers/AMD/EDID.cs @@ -0,0 +1,57 @@ +using System.Linq; +using System.Runtime.InteropServices; + +namespace PowerControl.Helpers.AMD +{ + internal class EDID + { + internal static byte[]? GetEDID(int displayIndex = ADL.ADL_DEFAULT_DISPLAY) + { + return ADLContext.WithSafe((context) => + { + byte[] edid = new byte[0]; + + for (int block = 0; block < ADL.ADL_MAX_EDID_EXTENSION_BLOCKS; ++block) + { + ADLDisplayEDIDData displayEdidData = new ADLDisplayEDIDData() + { + iSize = Marshal.SizeOf(), + iBlockIndex = block, + }; + + int res = ADL.ADL2_Display_EdidData_Get(context.Context, ADL.ADL_DEFAULT_ADAPTER, ADL.ADL_DEFAULT_DISPLAY, ref displayEdidData); + if (res != 0) + break; + + var blockBytes = displayEdidData.cEDIDData.Take(displayEdidData.iEDIDSize); + edid = edid.Concat(blockBytes).ToArray(); + } + + return edid; + }); + } + + internal static bool? SetEDID(byte[] value, int displayIndex = ADL.ADL_DEFAULT_DISPLAY) + { + return ADLContext.WithSafe((context) => + { + var bytes = new byte[ADL.ADL_MAX_EDIDDATA_SIZE * 2]; + value.CopyTo(bytes, 0); + + var blockData = new ADLDisplayEDIDDataX2() + { + // TODO: Hack to send a full EDID at once + iSize = Marshal.SizeOf(), + cEDIDData = bytes, + iEDIDSize = value.Length, + }; + + int res = ADL.ADL2_Display_EdidData_Set(context.Context, + ADL.ADL_DEFAULT_ADAPTER, ADL.ADL_DEFAULT_DISPLAY, + ref blockData); + + return res == 0; + }); + } + } +} diff --git a/PowerControl/Resources.Designer.cs b/PowerControl/Resources.Designer.cs index 48a2d72..0648e80 100644 --- a/PowerControl/Resources.Designer.cs +++ b/PowerControl/Resources.Designer.cs @@ -60,6 +60,16 @@ namespace PowerControl { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] CRU_SteamDeck { + get { + object obj = ResourceManager.GetObject("CRU_SteamDeck", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// diff --git a/PowerControl/Resources.resx b/PowerControl/Resources.resx index 716a078..fe2cdb2 100644 --- a/PowerControl/Resources.resx +++ b/PowerControl/Resources.resx @@ -118,6 +118,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Resources\CRU_SteamDeck.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + Resources\traffic-light-outline_light.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/PowerControl/Resources/CRU_SteamDeck.bin b/PowerControl/Resources/CRU_SteamDeck.bin new file mode 100644 index 0000000..575bc65 Binary files /dev/null and b/PowerControl/Resources/CRU_SteamDeck.bin differ diff --git a/RELEASE.md b/RELEASE.md index 0c03f04..d1815e9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,6 +9,7 @@ ## 0.6.x +- PowerControl: Install custom resolutions (EDID) (experimental feature) - SteamController: Add `X360: No Touchpads` profile - All: Show `Missing RTSS` button to install RTSS - PowerControl: Retain FPS Limit (proportion) on refresh rate change