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

发布于 2024-09-01 21:53:52 字数 3387 浏览 6 评论 0原文

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

显示 ID 可以持续存在 进程和系统重新启动,以及 通常只要保持不变 某些显示参数不 改变。

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

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

  1. 笔记本电脑开机。
  2. 内置液晶显示屏 屏幕由英特尔芯片组驱动。 显示 ID:30002
  3. 外部 显示器已插入。
  4. 内置 液晶屏切换至 nVidia 芯片组。它的显示 ID 更改: 30004
  5. 外部显示器已驱动 由 nVidia 芯片组。
  6. ...在此刻, Intel 芯片组处于休眠状态...
  7. 用户拔掉外部显示器
  8. 内置液晶屏切换回 英特尔芯片组。这是显示 ID 更改回原始状态:30002

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


想法关于:

我注意到显示器 ID 仅改变了 2,但我没有足够的测试 Mac 来确定这是否是所有新 MacBook Pro 的共同点,还是只是我的。无论如何,如果“只检查彼此相差 +/-2 的显示 ID”有效的话,这就是一种拼凑。


尝试:

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");

上面发生的情况是:

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

不幸的是,如果同一显示器更改了 GPU,IODisplayMatchDictionaries() 不会返回 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";
}

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

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


非常感谢任何帮助! :)

In Mac OS X, every display gets a unique CGDirectDisplayID number assigned to it. You can use CGGetActiveDisplayList() or [NSScreen screens] to access them, among others. Per Apple's docs:

A display ID can persist across
processes and system reboot, and
typically remains constant as long as
certain display parameters do not
change.

On newer mid-2010 MacBook Pro's, Apple started using auto-switching Intel/nVidia graphics. Laptops have two GPU's, a low-powered Intel, and a high-powered nVidia. Previous dual-GPU laptops (2009 models) didn't have auto-GPU switching, and required the user to make a settings change, logoff, and then logon again to make a GPU switch occur. Even older systems only had one GPU.

There's an issue with the mid-2010 models where CGDirectDisplayID's don't remain the same when a display switches from one GPU to the next. For example:

  1. Laptop powers on.
  2. Built-In LCD
    Screen
    is driven by Intel chipset.
    Display ID: 30002
  3. External
    Display
    is plugged in.
  4. Built-In
    LCD Screen switches to nVidia
    chipset. It's display ID changes:
    30004
  5. External Display is driven
    by nVidia chipset.
  6. ...at this point,
    the Intel chipset is dormant...
  7. User unplugs External Display.
  8. Built-In LCD Screen switches back to
    Intel chipset. It's display ID
    changes back to original: 30002

My question is, how can I match an old display ID to a new display ID when they alter due to a GPU change?


Thought about:

I've noticed that the display ID only changes by 2, but I don't have enough test Mac's available to determine if this is common to all new MacBook Pro's, or just mine. Kind of a kludge if "just check for display ID's which are +/-2 from one another" works, anyway.


Tried:

CGDisplayRegisterReconfigurationCallback(), which notifies before-and-after when displays are going to change, has no matching logic. Putting something like this inside a method registered with it doesn't work:

// 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");

What's happening above is:

  1. I'm caching oldInfoDict before display settings change.
  2. Waiting for display settings to change
  3. Then comparing oldInfoDict to newInfoDict by using IODisplayMatchDictionaries()
  4. IODisplayMatchDictionaries() returns a BOOL, either YES they're the same, or NO they're different.

Unfortunately, IODisplayMatchDictionaries() doesn't return YES if the same display changed GPU's. Here's an example of the dictionary's it's comparing (look at the IODisplayLocation key):

// 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";
}

As you can see, the IODisplayLocation key changes when GPU's are switched, hence IODisplayMatchDictionaries() doesn't work.

I can, theoretically, compared just the DisplayProductID and DisplayVendorID keys, but I'm writing end-user software, and am worried of a situation where users have two or more identical monitors plugged in (meaning they'll both have the same DisplayProductID/DisplayVendorID). In other words, it's a less-than-perfect solution open to potential glitches.


Any help is greatly appreciated! :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

夜司空 2024-09-08 21:53:52

使用 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 中可用。

Use CFUUIDRef which can be obtained using:

CGDisplayCreateUUIDFromDisplayID(CGDirectDisplayID displayID)
and you can get the display ID back using:

CGDisplayGetDisplayIDFromUUID(CFUUIDRef uuid)

This is what I'm using to uniquely identify displays even when their CGDirectDisplayID changes, for example was plugged into a different port. These functions aren't properly documented by Apple unfortunately, but my testing on multiple Macs with multiple displays shown that the CFUUIDRef obtained is unique and consistent -even after a reboot-, regardless of whether CGDirectDisplayID changed for whatever reason.

To check if a display is new/unique, take its CGDirectDisplayID and convert it to CFUUIDRef, and then compare the UUID, it is a many-to-one relationship, many CGDirectDisplayIDs will map to a single CFUUIDRef.

These API calls are available in ApplicationServices in 10.7 - 10.12, and ColorSync since 10.13.

叹沉浮 2024-09-08 21:53:52

从概念上讲,我没有找到比您列出的“尝试过”更好的方法。但我找到了一个解决方案来解决仅比较供应商 ID 和产品 ID 的歧义问题。

代码中的 oldInfoDictnewInfoDict 包含键 kIODisplayEDIDKey 的附加条目(在 IOGraphicsTypes.h),其中包含 EDID。我的观察表明,这些数据作为一个整体在 GPU 交换机之间保持不变。例如:

    CGDirectDisplayID displayId = [[[screen deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue];
    io_service_t displayPort = CGDisplayIOServicePort(displayId);

    if (displayPort == MACH_PORT_NULL)
        return nil;  // No physical device to get a name from.

    CFDictionaryRef infoDict = IODisplayCreateInfoDictionary(displayPort, kIODisplayOnlyPreferredName);

    NSData *displayEdid = (NSData *)CFDictionaryGetValue(infoDict, CFSTR(kIODisplayEDIDKey));
    NSLog(@"EDID: %@", displayEdid);

    CFRelease(infoDict);

查看维基百科中EDID的数据描述,这个blob已经包含制造商、产品id和系列。因此,使用 EDID 数据(或者例如,如果您只想比较较短的数字,则使用它的哈希值)来比较显示器就足够了。

I have found no conceputally better way than what you list as "Tried". But I found a solution for the ambiguity issue of comparing only vendor id and product id.

oldInfoDict and newInfoDict in your code contain an additional entry for key kIODisplayEDIDKey (defined in IOGraphicsTypes.h) which contains the EDID of each connected display. My observations show that this data as a whole stays persistent between GPU switches. For example:

    CGDirectDisplayID displayId = [[[screen deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue];
    io_service_t displayPort = CGDisplayIOServicePort(displayId);

    if (displayPort == MACH_PORT_NULL)
        return nil;  // No physical device to get a name from.

    CFDictionaryRef infoDict = IODisplayCreateInfoDictionary(displayPort, kIODisplayOnlyPreferredName);

    NSData *displayEdid = (NSData *)CFDictionaryGetValue(infoDict, CFSTR(kIODisplayEDIDKey));
    NSLog(@"EDID: %@", displayEdid);

    CFRelease(infoDict);

Looking at the data description of EDID in Wikipedia, this blob already contains manufacturer, product id and serial. So it's enough to compare displays by using the EDID data (or for example a hash of it if you only want to compare a shorter number).

忆悲凉 2024-09-08 21:53:52

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

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

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

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

While I'm no pro, I believe the answer is to allow Apple to notify you when the user changes displays. The info inf the callback contains flags for adding and removing CGDirectDisplayIDs.

The user shouldn't be adding or removing graphics cards during operation, so I would play with making list at startup, and whenever you get the "remove" flag set the next "add" operation to replace that ID in the list.

I'd try just printing the information you get back each time CGDisplayRegisterReconfigurationCallback calls your function. See if you get one with a DeviceUID with a 'remove' flag, and then a subsequent call another with an 'add' flag. Checking those id's against CGGetActiveDisplayList would also aid in understanding what's going on.

That's my best bet, hope it helps!

逆光飞翔i 2024-09-08 21:53:52

我一直在使用:

-(NSString *)uuidForDisplayID:(CGDirectDisplayID)displayID
{
    CFUUIDRef uuidRef = CGDisplayCreateUUIDFromDisplayID (displayID);
    CFStringRef string = CFUUIDCreateString (kCFAllocatorDefault, uuidRef);
    CFRelease (uuidRef);
    
    return ([(NSString *)string autorelease]);
}

但是,在回调中的显示删除事件期间,即使在初始 kCGDisplayBeginConfigurationFlag 期间,对 CGDisplayCreateUUIDFromDisplayID 的调用也会失败,并出现“无效的 displayID”错误,因此当显示器被移除时,我认为没有办法从不明确的 CGDirectDisplayID 回到稳定的 UUID。

有趣的是,我们仍然可以获得 NSScreen 数组并搜索与 CGDirectDisplayID 匹配的 NSScreen 数组:

[[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedLongValue]

在 Xcode 中,NSScreen 有一个名为 _UUIDString 的属性,它包含相同的 UUID,但我看不出有什么办法可以从 NSScreen 中获取这个 UUID。

I have been using:

-(NSString *)uuidForDisplayID:(CGDirectDisplayID)displayID
{
    CFUUIDRef uuidRef = CGDisplayCreateUUIDFromDisplayID (displayID);
    CFStringRef string = CFUUIDCreateString (kCFAllocatorDefault, uuidRef);
    CFRelease (uuidRef);
    
    return ([(NSString *)string autorelease]);
}

However during a display removal event in the call back, even during the initial kCGDisplayBeginConfigurationFlag, the call to CGDisplayCreateUUIDFromDisplayID fails with an 'invalid displayID' error, thus I see no way to go from a ambiguous CGDirectDisplayID back to a stable UUID when a display is being removed.

Interestingly, one can still get the array of NSScreens and search for one matching the CGDirectDisplayID:

[[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedLongValue]

In Xcode the NSScreen has a property called _UUIDString which contains the same UUID but I see no way to get this UUID out of the NSScreen.

臻嫒无言 2024-09-08 21:53:52

刚刚找到这篇文章,并根据我观察到的事情进行了一些挖掘。也许 /Library/Preferences/com.apple.windowserver.plist 中有有用的信息。它是 plist 格式,因此应该使用此命令将其解码到终端:

plutil -p /Library/Preferences/com.apple.windowserver.plist

如果您发现有用的内容,请回复。

Just found this post, and did some digging based on things I've observed. Maybe there is useful information in /Library/Preferences/com.apple.windowserver.plist. It's in plist format, so this command should be used to decode it to the terminal:

plutil -p /Library/Preferences/com.apple.windowserver.plist

If you find something useful, please respond.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文