BartoszCichecki / LenovoLegionToolkit

Lightweight Lenovo Vantage and Hotkeys replacement for Lenovo Legion laptops.
GNU General Public License v3.0
5.54k stars 249 forks source link

[BUG]: GPU Working mode can't change between Hybrid Mode and Hybrid-iGPU only Mode #236

Closed RoadToDream closed 2 years ago

RoadToDream commented 2 years ago

Version

2.6.0

OS

Windows 11 22H2

Device

Legion 7i 16IAX7

BIOS version

K1CN31WW

What's wrong?

When system has only iGPU enabled, choosing Hybrid Mode will not reenable dGPU. When system has both iGPU and dGPU enabled, choosing Hybrid-iGPU only Mode will not disable dGPU.

How to reproduce the bug?

  1. Open LLT
  2. Select between Hybrid-iGPU only Mode and Hybrid Mode.
  3. iGPU or dGPU wouldn't enable or disable as expected.

What is the behavior that you expected?

iGPU or dGPU would enable or disable according to GPU mode.

Logs

log_2022_09_28_18_16_09.txt

Do you have Lenovo software installed?

Did you disable any Lenovo software using Lenovo Legion Toolkit?

Additional information

This issue only happens when Vantage is disabled. If Vantage service is enabled, GPU states can change accordingly. May I ask if this is expected behavior?

BartoszCichecki commented 2 years ago

To clarify: You have set the mode to iGPU-only and the GPU is ejected from the system. Then you change back to Hybrid and dGPU doesn't get reconnected?

RoadToDream commented 2 years ago

To clarify: You have set the mode to iGPU-only and the GPU is ejected from the system. Then you change back to Hybrid and dGPU doesn't get reconnected?

Hi! Thanks for the reply. Both operations will not work correctly when Vantage service is disabled. While change from iGPU-only to Hybrid may work occasionally. If Vantage service is enabled, switch between GPU mode will still probably not work. Control via Vantage directly can change GPU states correctly. I have debugged program on my laptop using Visual Studio version 2022, and no exception is thrown.

I am willing to provide more info if required.

BartoszCichecki commented 2 years ago

I can see that something might be missing compared to what Vantage is doing. I made a quick patch that may or may not work - we need to do some trouble shooting.

You can try the build below, maybe it's gonna work?

LenovoLegionToolkitSetup_potentialfix.zip

BartoszCichecki commented 2 years ago

You can also see the changes in the branch linked to this issue.

RoadToDream commented 2 years ago

Thanks for the quick fix. I have just installed this beta build and it seems it doesn't fix the issue. Let me know if there is anything I may help :)

BartoszCichecki commented 2 years ago

What about this one?

LenovoLegionToolkitSetup_fix2.zip

RoadToDream commented 2 years ago

No. In addition, there will be a warning message shown up when first start or on exit. image

RoadToDream commented 2 years ago

I am no expert in .NET programming, but I would love to send a list of available method in WMI. By comparing list and code I can see LLT currently implement SetGSyncStatus and SetIGPUModeStatus as a general method in AbstractLenovoGamezoneWmiFeature. My quick guess is that maybe NotifyDGPUStatus also contributes to this process and may need implemented also. This function seems like a callback function that should be called to notify system dGPU changes after ACPI disabled/enabled dGPU.

Hope this may help a little.

Active
CompareTo
Container
ConvertFromDateTime
ConvertToDateTime
Disposed
Equals
GetBIOSOCMode
GetCpuFrequency
GetCPUTemp
GetDGPUHWId
GetFan1Speed
GetFan2Speed
GetFanCoolingStatus
GetFanCount
GetFanMaxSpeed
GetGpuGpsState
GetGPUOCPow
GetGPUOCType
GetGPUPow
GetGPUTemp
GetGSyncStatus
GetHardwareInfoSupportVersion
GetHashCode
GetIGPUModeStatus
GetIntelligentSubMode
GetIRTemp
GetKeyboardfeaturelist
GetKeyboardLight
GetLearningProfileCount
GetLifetimeService
GetMacrokeyCount
GetMacrokeyScancode
GetMemoryOCInfo
GetODStatus
GetPowerChargeMode
GetProductInfo
GetPropertyQualifierValue
GetPropertyValue
GetQualifierValue
GetSmartFanMode
GetSmartFanSetting
GetText
GetThermalMode
GetThermalTableID
GetTPStatus
GetTriggerTemperatureValue
GetType
GetVersion
GetWaterCoolingStatus
GetWinKeyStatus
InitializeLifetimeService
InstanceName
IsACFitForOC
IsBIOSSupportOC
IsChangedYLog
IsRestoreOCValue
IsSupportCpuOC
IsSupportDisableTP
IsSupportDisableWinKey
IsSupportFanCooling
IsSupportGpuOC
IsSupportGSync
IsSupportIGPUMode
IsSupportLightingFeature
IsSupportOD
IsSupportSmartFan
IsSupportWaterCooling
Item
NotifyDGPUStatus
Properties
PSComputerName
Qualifiers
SetBIOSOC
SetDDSControlOwner
SetFanCooling
SetGpuGpsState
SetGSyncStatus
SetIGPUModeStatus
SetIntelligentSubMode
SetKeyboardLight
SetLightControlOwner
SetODStatus
SetPropertyQualifierValue
SetPropertyValue
SetQualifierValue
SetSmartFanMode
SetThermalTableID
SetTPStatus
SetWaterCoolingStatus
SetWinKeyStatus
Site
SystemProperties
RoadToDream commented 2 years ago

I think my previous guess is correct. Adding NotifyDGPUStatus will guarantee GPU switch.

function Toggle-iGPUState {
    $LenovoWMIPath=(Get-WmiObject -Namespace "root\WMI" -Query "SELECT * FROM LENOVO_GAMEZONE_DATA").__PATH
    $LenovoWMI=[WMI]$LenovoWMIPath
    $CurrentState=$LenovoWMI.GetIGPUModeStatus().Data
    $TargetState=[int]($CurrentState -xor 1)
    $LenovoWMI.SetIGPUModeStatus($TargetState)
    while ($LenovoWMI.GetIGPUModeStatus().Data -ne $TargetState) 
    {
        Start-Sleep -Milliseconds 200
    }
    $LenovoWMI.NotifyDGPUStatus($TargetState)
}
RoadToDream commented 2 years ago

Hi, I have done a quick fix for this issue, but didn't check for cross-BIOS compatibility. Just consider this pull request as a success sample for model 16IAX7. Great thanks for bringing this useful tool!

BartoszCichecki commented 2 years ago

OK so this build was tested and other guys claim that it works: LenovoLegionToolkitSetup.zip

Could you please verify?

RoadToDream commented 2 years ago

Hi, I have tested it and it works. Seems to be a little slow than my build but that may be due to ACPI. I think it's all good.

BartoszCichecki commented 2 years ago

I was trying with listening for WM_DEVICECHANGE message, because that's what Vantage seems to be doing (instead I force device refresh manually) but the msg didn't arrive for some reason, idk why.

RoadToDream commented 2 years ago

I was trying with listening for WM_DEVICECHANGE message, because that's what Vantage seems to be doing (instead I force device refresh manually) but the msg didn't arrive for some reason, idk why.

I guess there might still be some dirty IPC communicate between Vantage and services, but I am happy with the current one works (Just not sure if it also works on Gen 6 Legions).

BartoszCichecki commented 2 years ago

Just out of curiosity, could you try this one too: LenovoLegionToolkitSetup_prolly_broken.zip

I would appreciate if you send a log too.

RoadToDream commented 2 years ago

Sure I can do it. May I ask what change is introduced to this build?

BartoszCichecki commented 2 years ago

Sure, here is the patch file: patch.diff.txt

BartoszCichecki commented 2 years ago

And also added this:

    internal class SystemEventInterceptor : NativeWindow
    {
        private static int WM_DEVICECHANGE = 0x0219;

        public SystemEventInterceptor(IntPtr handle)
        {
            AssignHandle(handle);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_DEVICECHANGE)
            {
                if (Log.Instance.IsTraceEnabled)
                    Log.Instance.Trace($"WM_DEVICECHANGE");

                NotifyDGPU();
            }

            base.WndProc(ref m);
        }

        private async void NotifyDGPU()
        {
            var feat = IoCContainer.Resolve<IGPUModeFeature>();

            var state = await feat.GetStateAsync();
            await feat.NotifyDGPUStatusAsync(state);
        }
    }

Stil trying to figure out the message thing

BartoszCichecki commented 2 years ago

Or actually, nevermind that won't work :/

RoadToDream commented 2 years ago

That seems working. But will have an error shown below. image Here are log and error. error_2022_10_01_21_30_37.txt log_2022_10_01_21_30_25.txt

BartoszCichecki commented 2 years ago

LenovoLegionToolkitSetup_test70.zip

And this one?

RoadToDream commented 2 years ago

This one is good. It's as fast as my original build (or maybe even better).

BartoszCichecki commented 2 years ago

And the "eject dGPU" appears/disappears correctly?

RoadToDream commented 2 years ago

Yes.

BartoszCichecki commented 2 years ago

Could you grab a log, just to verify?

RoadToDream commented 2 years ago

log_2022_10_01_21_40_33.txt Sure. Logs looks all good.

RoadToDream commented 2 years ago

Let me check power consumption to verify.

RoadToDream commented 2 years ago

All good.

BartoszCichecki commented 2 years ago

That is sooo confusing, because now only pnputil works.... The notifydgpustate doesn't work for some reason:

internal class SystemEventInterceptor : NativeWindow
    {
        private static int WM_DEVICECHANGE = 0x0219;

        private static int DBT_DEVTYP_HANDLE = 5;
        private static Guid GUID_DISPLAY_DEVICE_ARRIVAL = new("1CA05180-A699-450A-9A0C-DE4FBE3DDD89");

        public SystemEventInterceptor(IntPtr handle)
        {
            AssignHandle(handle);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_DEVICECHANGE)
            {
                if (Log.Instance.IsTraceEnabled)
                    Log.Instance.Trace($"WM_DEVICECHANGE");

                if (m.LParam != IntPtr.Zero)
                {
                    if (Log.Instance.IsTraceEnabled)
                        Log.Instance.Trace($"m.LParam != IntPtr.Zero");

                    var devBroadcastHdr = Marshal.PtrToStructure<DevBroadcastHdr>(m.LParam);
                    if (devBroadcastHdr.DeviceType == DBT_DEVTYP_HANDLE)
                    {
                        if (Log.Instance.IsTraceEnabled)
                            Log.Instance.Trace($"devBroadcastHdr.DeviceType == DBT_DEVTYP_HANDLE");

                        var devBroadcastDeviceInterface = Marshal.PtrToStructure<DevBroadcastDeviceInterface>(m.LParam);
                        if (devBroadcastDeviceInterface.ClassGuid == GUID_DISPLAY_DEVICE_ARRIVAL)
                        {
                            if (Log.Instance.IsTraceEnabled)
                                Log.Instance.Trace($"devBroadcastDeviceInterface.ClassGuid == GUID_DISPLAY_DEVICE_ARRIVAL");

                            if (Log.Instance.IsTraceEnabled)
                                Log.Instance.Trace($"NotifyDGPU");

                            NotifyDGPU();
                        }
                    }
                }
            }

            base.WndProc(ref m);
        }

        private async void NotifyDGPU()
        {
            var feat = IoCContainer.Resolve<IGPUModeFeature>();

            if (!await feat.IsSupportedAsync())
                return;

            if (Log.Instance.IsTraceEnabled)
                Log.Instance.Trace($"Notifying...");

            var state = await feat.GetStateAsync();
            await feat.NotifyDGPUStatusAsync(state);
        }
    }

If you look at the log, it seems like m.LParam is always zero pointer.

BartoszCichecki commented 2 years ago

I pushed this code to bug/more-investigation-hybrid you can take a look if you want.

RoadToDream commented 2 years ago

I have no idea here... I will look into the code in branch you mentioned later.

One maybe unhelpful comment here: For LLT without any modifications we mentioned here, if LLT doesn't change the state successfully, then I go to Vantage to change the state. Windows will go blue screen with error code VIDEO_DXGKRNL_FATAL_ERROR.

BartoszCichecki commented 2 years ago

OK, one more :D This one should handle correct messages.

LenovoLegionToolkitSetup_test71.zip

RoadToDream commented 2 years ago

No luck for this one. It's not working. (I guess pnputil may work sometimes but it is not guaranteed here) log_2022_10_01_22_30_09.txt

RoadToDream commented 2 years ago

Just curious, does any other user experienced the same issue? It seems I am the only one that can't switch graphics card successfully, if so I guess it's due to my environment and this issue is a false alert. I can continue use my patched LLT and that's totally fine.

BartoszCichecki commented 2 years ago

No you are not, the fix you made is tested on couple of other machines and it works better. The problem is we can't figure out the proper order of operations with SetIGPUModeState and NotifyDGPUState. There are still some problems with waking/sleeping DGPU when connecting/disconnecting screens in iGPU-only mode.

At this point I think the only thing left that we can do is either completely reverse engineer GraphicsCardSettings.dll from Vantage/LegionZone or set up API monitor and monitor calls that Legion Zone is making (it's easier to trace Legion Zone than to trace Vantage) to figure out what to send where/when. There is still the missing piece of calling NotifyDGPUState when WM_DEVICECHANGE message is delivered.

If you would like to join the dicussion, join the Legion Series discord and ping me @bc. I can loop you in all the discussions/tracing we do there.

BartoszCichecki commented 2 years ago

The implementation still seems to bug out after some time, so there is def something wrong or the implementation of this is just "broken-ish". This is why I think that we need to trace the WMI calls to get an idea what is sent when.

KarlLee830 commented 2 years ago

Just curious, does any other user experienced the same issue? It seems I am the only one that can't switch graphics card successfully, if so I guess it's due to my environment and this issue is a false alert. I can continue use my patched LLT and that's totally fine.

I had the same problem as you, but I saw that there was already the same issue, so I didn't create a new one.

RoadToDream commented 2 years ago

Then it still worth digging deeper. Let me first do a WMI logging and then take a look into Legion Zone. Vantage doesn't seems very easy as most of its functions are done in a DLL and looks like all functions are wrapped in a single factory call.

advanced03 commented 2 years ago

To clarify: You have set the mode to iGPU-only and the GPU is ejected from the system. Then you change back to Hybrid and dGPU doesn't get reconnected?

I'm having the same issue, 16ARH7H JUCN Bios

BartoszCichecki commented 2 years ago

Done. Tested on 16IAX7, K1CN31WW bios.

RoadToDream commented 2 years ago

Done. Tested on 16IAX7, K1CN31WW bios.

Can confirm the issue is fixed. Thank you for the all the work!

advanced03 commented 2 years ago

Done. Tested on 16IAX7, K1CN31WW bios.

Can confirm the issue is fixed. Thank you for the all the work!

How can I test if it's fixed?

RoadToDream commented 2 years ago

How can I test if it's fixed?

You may compile from source before Bartosz releases a new version.

Letronxis commented 1 year ago

However when turn Hybrid -> Hybrid iGPU, the dGPU still enable(sleep) not disable completely

BartoszCichecki commented 1 year ago

However when turn Hybrid -> Hybrid iGPU, the dGPU still enable(sleep) not disable completely

dGPU must not be in use for the switch to be done properly. Lenovo designed in a way, where sometimes it just doesn't work. Nothing I can do here.