如何解决CGDirectDisplayID更改Core Foundation/IO Kit中较新的多GPU Apple笔记本电脑的问题?

Dav*_*her 10 core-graphics objective-c iokit core-foundation multi-gpu

在Mac OS X中,每个显示都会CGDirectDisplayID分配一个唯一的编号.您可以使用CGGetActiveDisplayList()或[NSScreen screens]访问它们等.根据Apple的文档:

显示ID可以在进程和系统重新引导之间持续存在,并且只要某些显示参数不变,通常保持不变.

在2010年年中的MacBook Pro上,Apple开始使用自动切换Intel/nVidia显卡.笔记本电脑有两个GPU,一个低功耗的英特尔和一个高性能的nVidia.以前的双GPU笔记本电脑(2009型号)没有自动GPU切换,需要用户进行设置更改,注销,然后再次登录以进行GPU切换.即使是较旧的系统也只有一个GPU.

2010年中期的模型存在一个问题,当显示器从一个GPU切换到另一个GPU时,CGDirectDisplayID不会保持不变.例如:

  1. 笔记本电脑启动.
  2. 内置LCD屏幕由Intel芯片组驱动.显示ID: 30002
  3. 外接显示器已插入.
  4. 内置 LCD屏幕切换到nVidia芯片组.它的显示ID更改: 30004
  5. 外部显示器由nVidia芯片组驱动.
  6. ......此时,英特尔芯片组处于休眠状态......
  7. 用户拔出外部显示器.
  8. 内置LCD屏幕切换回英特尔芯片组.它的显示ID更改回原始版本:30002

我的问题是,如果由于GPU更改而改变时,如何将旧显示ID与新显示ID匹配?


想到:

我注意到显示ID只改变了2,但我没有足够的测试Mac可用来确定这是否适用于所有新MacBook Pro,或者只是我的.无论如何,如果"仅检查彼此+/- 2的显示ID",那就是一种kludge.


尝试:

CGDisplayRegisterReconfigurationCallback(),当显示器要改变时通知前后,没有匹配的逻辑.在这个注册的方法中放置这样的东西是行不通的:

// Run before display settings change:
CGDirectDisplayID directDisplayID = ...;
io_service_t    servicePort = CGDisplayIOServicePort(directDisplayID);
CFDictionaryRef oldInfoDict = IODisplayCreateInfoDictionary(servicePort, kIODisplayMatchingInfo);

// ...display settings change...

// Run after display settings change:
CGDirectDisplayID directDisplayID = ...;
io_service_t    servicePort = CGDisplayIOServicePort(directDisplayID);
CFDictionaryRef newInfoDict = IODisplayCreateInfoDictionary(servicePort, kIODisplayMatchingInfo);
BOOL match = IODisplayMatchDictionaries(oldInfoDict, newInfoDict, 0);

if (match)
    NSLog(@"Displays are a match");
else
    NSLog(@"Displays are not a match");
Run Code Online (Sandbox Code Playgroud)

上面发生的是:

  1. 我在显示设置更改之前缓存oldInfoDict.
  2. 等待显示设置更改
  3. 然后比较oldInfoDictnewInfoDict使用IODisplayMatchDictionaries()
  4. IODisplayMatchDictionaries() 返回一个BOOL,或者是YES它们是相同的,或者NO它们是不同的.

不幸的是,IODisplayMatchDictionaries()如果相同的显示改变了GPU ,则不会返回YES.这是一个比较字典的例子(看一下IODisplayLocation键):

// oldInfoDict  (Display ID: 30002)
oldInfoDict: {
    DisplayProductID = 40144;
    DisplayVendorID = 1552;
    IODisplayLocation = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/IGPU@2/AppleIntelFramebuffer/display0/AppleBacklightDisplay";
}

// newInfoDict  (Display ID: 30004)
newInfoDict: {
    DisplayProductID = 40144;
    DisplayVendorID = 1552;
    IODisplayLocation = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/P0P2@1/IOPCI2PCIBridge/GFX0@0/NVDA,Display-A@0/NVDA/display0/AppleBacklightDisplay";
}
Run Code Online (Sandbox Code Playgroud)

如您所见,IODisplayLocation当GPU切换时,键会发生变化,因此IODisplayMatchDictionaries()不起作用.

理论上,我可以比较DisplayProductIDDisplayVendorID键,但我正在编写最终用户软件,并且担心用户插入了两个或更多相同的监视器(意味着它们都具有相同的DisplayProductID/DisplayVendorID) .换句话说,这是一个不太完美的解决方案,可能会出现潜在的故障.


任何帮助是极大的赞赏!:)

H. *_*mri 7

使用 CFUUIDRef 可以通过以下方式获得:

CGDisplayCreateUUIDFromDisplayID(CGDirectDisplayID displayID) 您可以使用以下方法取回显示 ID:

CGDisplayGetDisplayIDFromUUID(CFUUIDRef uuid)

这就是我用来唯一标识显示器的方法,即使它们的 CGDirectDisplayID 发生变化,例如插入不同的端口。不幸的是,Apple 没有正确记录这些功能,但我在具有多个显示器的多台 Mac 上的测试表明,获得的 CFUUIDRef 是唯一且一致的 - 即使在重新启动后 - 无论 CGDirectDisplayID 是否因任何原因发生变化。

要检查一个显示是否是新的/唯一的,取其 CGDirectDisplayID 并将其转换为 CFUUIDRef,然后比较 UUID,这是一个多对一的关系,许多 CGDirectDisplayID 将映射到单个 CFUUIDRef。

这些 API 调用在 10.7 - 10.12 中的 ApplicationServices 和 10.13 以后的 ColorSync 中可用。


Ste*_*ani 1

虽然我不是专业人士,但我相信答案是让 Apple在用户更改显示时通知您。回调中的信息包含添加和删除的标志CGDirectDisplayID

用户不应该在操作期间添加或删除显卡,因此我会在启动时制作列表,并且每当您获得“删除”标志时,都会设置下一个“添加”操作来替换列表中的该 ID。

CGDisplayRegisterReconfigurationCallback我会尝试只打印每次调用函数时返回的信息。看看您是否得到一个带有“删除”标志的 DeviceUID,然后随后调用另一个带有“添加”标志的设备UID。检查这些 IDCGGetActiveDisplayList也有助于了解发生了什么。

这是我最好的选择,希望有帮助!

  • 谢谢斯蒂芬。遗憾的是,通知中没有任何内容说明“DisplayID ABC 现已变为 DisplayID XYZ”。自从发布这个问题以来我发现显示 ID 可以在运行时随时动态更改。例如,如果您启动 Adob​​e Photoshop CS4,则 2010 年中期的 MacBook Pro 将从 Intel 集成显卡切换为 nVidia 独立显卡。这是有道理的,因为 Photoshop CS4+ 是 GPU 密集型的。但是,显示器的显示 ID 在 GPU 切换期间会发生变化。一旦您启动 Photoshop,它就会回退到 Intel GPU,并且 displayID 再次发生变化。乐趣!:) (2认同)