Metal框架详细解析(十一) —— 基本组件之器件选择 - 图
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.10.07 星期日 |
前言
很多做视频和图像的,相信对这个框架都不是很陌生,它渲染高级3D图形,并使用GPU执行数据并行计算。接下来的几篇我们就详细的解析这个框架。感兴趣的看下面几篇文章。
1. Metal框架详细解析(一)—— 基本概览
2. Metal框架详细解析(二) —— 器件和命令(一)
3. Metal框架详细解析(三) —— 渲染简单的2D三角形(一)
4. Metal框架详细解析(四) —— 关于GPU Family 4(一)
5. Metal框架详细解析(五) —— 关于GPU Family 4之关于Imageblocks(二)
6. Metal框架详细解析(六) —— 关于GPU Family 4之关于Tile Shading(三)
7. Metal框架详细解析(七) —— 关于GPU Family 4之关于光栅顺序组(四)
8. Metal框架详细解析(八) —— 关于GPU Family 4之关于增强的MSAA和Imageblock采样覆盖控制(五)
9. Metal框架详细解析(九) —— 关于GPU Family 4之关于线程组共享(六)
10. Metal框架详细解析(十) —— 基本组件(一)
Device Selection and Fallback for Graphics Rendering - 图形渲染的器件选择和后退
演示如何使用多个GPU并高效渲染到显示器。
本篇是macOS相关,要是看关于iOS的请忽略这篇文章。
Overview - 概览
macOS
支持具有多个GPU和显示的系统。 一个例子是MacBook Pro,它具有低功耗集成GPU,高性能独立GPU,强大的外部GPU和其他显示器。 Metal
应用必须仔细选择能够最大化特定显示效率和性能的GPU。 他们还应该优雅地响应任何GPU或显示更改,例如当用户断开外部GPU或在显示器之间移动窗口时。
Drawables, Displays, and GPUs
应用程序中的每个视图都显示在一个显示器上,每个显示器由一个GPU驱动。 要在视图中显示图形内容,视图的显示将显示来自显示器驱动GPU的渲染图形。
如果您的应用使用不会驱动视图显示的GPU进行渲染,则系统必须先将渲染GPU中的可绘制内容复制到显示GPU,然后才能显示它。 这种传输可能很昂贵,因为GPU之间的带宽受连接它们的总线的限制。 外部GPU的费用更高,因为他们的Thunderbolt 3
总线带宽比内部PCI Express
总线少得多。
呈现drawable
的最快路径是使用驱动视图显示的GPU渲染可绘制的路径。 一个例子是带有独立GPU和集成GPU的MacBook Pro
,集成GPU可以在某些条件下驱动MacBook Pro
的显示器(由热状态,电池寿命或应用程序需求引起)。
另一个例子是Mac连接到外部GPU,外部GPU驱动外部显示器。
Transition Smoothly Between Devices - 在设备之间平滑转换
示例的视图控制器管理所有Metal
设备,每个设备代表不同的GPU。当示例运行viewDidLoad
方法时,视图控制器会为系统可用的每个设备初始化一个新的AAPLRenderer
。该示例一次只使用一个设备,但它会为每个设备初始化一个渲染器,以便在所有设备上预加载和镜像应用程序的Metal资源。因此,当应用程序在运行时在GPU之间切换时,样本在设备之间平滑过渡,因为等效资源已经可用并加载到每个设备上。这种预加载和镜像策略避免了如果样本在切换时需要加载资源则会出现的显着延迟。
注意:预加载和镜像资源允许您在设备之间平滑过渡,但它也会增加应用程序的总内存使用量。您必须仔细确定应预先加载和镜像哪些资源,以及只有当您的应用程序在设备之间切换时才应加载哪些资源。
Set the Optimal Device for the View’s Display - 为视图的显示设置最佳设备
视图显示后,示例将获取显示视图的显示的CGDirectDisplayID
值。 该示例使用此标识符来获取驱动显示的Metal
设备。
// Get the display ID of the display in which the view appears
CGDirectDisplayID viewDisplayID = (CGDirectDisplayID) [_view.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue];
// Get the Metal device that drives the display
id<MTLDevice> newPreferredDevice = CGDirectDisplayCopyCurrentMetalDevice(viewDisplayID);
该示例为视图控制器的MTKView
设置此设备,并选择与该设备关联的AAPLRenderer
来执行应用程序的渲染。 此设置可确保系统使用驱动显示器的设备进行渲染,并避免将任何可绘制的数据从一个GPU复制到另一个GPU。
Handle Display Change Notifications - 处理显示更改通知
为了跟上视图显示的最佳设备,样本注册了两个系统通知:
-
NSApplicationDidChangeScreenParametersNotification
。 当Mac的显示配置发生变化时,系统会发布此通知。 例如,用户将外部显示器与系统连接或断开连接。 另一个例子是当驱动显示器的GPU发生变化时,例如当启用自动图形切换(Automatic Graphics Switching)
并且系统在离散和集成GPU之间切换以驱动显示器时。 -
NSWindowDidChangeScreenNotification
。 当任何窗口(包括包含应用程序视图的窗口)移动到不同的显示时,系统会发布此通知。
// Register for the NSApplicationDidChangeScreenParametersNotification, which triggers
// when the system's display configuration changes
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleScreenChanges:)
name:NSApplicationDidChangeScreenParametersNotification
object:nil];
// Register for the NSWindowDidChangeScreenNotification, which triggers when the window
// changes screens
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleScreenChanges:)
name:NSWindowDidChangeScreenNotification
object:nil];
在这两种情况下,系统都会调用示例的handleScreenChanges:
方法来处理通知。 然后,样本通过选择与驱动显示器的设备对应的AAPLRenderer
对象,为视图的显示选择最佳设备。
Set a GPU Eject Policy - 设置GPU弹出策略
默认情况下,当应用程序使用的外部GPU从系统中删除时,macOS会完全重新启动应用程序。 应用通常通过以下方式处理重新启动:
-
1) 当系统调用应用程序的应用程序的
application willEncodeRestorableState:
方法时,在macOS退出应用程序之前,保存尽可能多的状态。 -
2) 在系统调用应用程序的
application:didDecodeRestorableState:
方法时,在macOS重新启动应用程序之后,恢复任何已保存的状态。
该示例避免了此应用程序重新启动例程,而是选择处理外部GPU移除本身,而不需要macOS
退出并重新启动应用程序。 示例的Info.plist
文件具有带有wait
值的GPUEjectPolicy
键,表示应用程序将通过响应Metal发布的相应通知来显式处理外部GPU的删除。
Register for External GPU Notifications - 注册外部GPU通知
该示例调用MTLCopyAllDevicesWithObserver()
函数以获取系统可用的所有Metal设备。 此方法允许样本提供MTLDeviceNotificationHandler
块,该块在系统中添加或删除外部GPU时执行。 此处理程序提供两个参数:
-
device
。 添加或删除的设备。 -
notifyName
。 描述触发通知的事件的值。
MTLDeviceNotificationHandler notificationHandler;
AAPLViewController * __weak controller = self;
notificationHandler = ^(id<MTLDevice> device, MTLDeviceNotificationName name)
{
[controller markHotPlugNotificationForDevice:device name:name];
};
// Query all supported metal devices with an observer, so the app can receive notifications
// when external GPUs are added to or removed from the system
id<NSObject> metalDeviceObserver = nil;
NSArray<id<MTLDevice>> * availableDevices =
MTLCopyAllDevicesWithObserver(&metalDeviceObserver,
notificationHandler);
Respond to External GPU Notifications - 响应外部GPU通知
通知处理程序可以在任何线程上执行。 但是,所有UI更新必须在主线程上进行,并且必须明确使应用程序的状态更改成为线程安全的。 为了符合这些线程要求,视图控制器使用@synchronized
指令保护对_hotPlugEvent
和_hotPlugDevice
实例变量的访问。 (@synchronized
指令是在Objective-C
代码中创建互斥锁的便捷方式。)
当发生通知时,该示例在markHotPlugNotificationForDevice:name:
方法中设置这些实例变量。
- (void)markHotPlugNotificationForDevice:(nonnull id<MTLDevice>)device
name:(nonnull MTLDeviceNotificationName)name
{
@synchronized(self)
{
if ([name isEqualToString:MTLDeviceWasAddedNotification])
{
_hotPlugEvent = AAPLHotPlugEventDeviceAdded;
}
else if ([name isEqualToString:MTLDeviceRemovalRequestedNotification])
{
_hotPlugEvent = AAPLHotPlugEventDeviceEjected;
}
else if ([name isEqualToString:MTLDeviceWasRemovedNotification])
{
_hotPlugEvent = AAPLHotPlugEventDevicePulled;
}
_hotPlugDevice = device;
}
}
该示例在主线程上读取这些实例变量,并在handlePossibleHotPlugEvent
方法中处理通知。
- (void)handlePossibleHotPlugEvent
{
AAPLHotPlugEvent hotPlugEvent;
id<MTLDevice> hotPlugDevice;
@synchronized(self)
{
hotPlugEvent = _hotPlugEvent;
hotPlugDevice = _hotPlugDevice;
_hotPlugDevice = nil;
}
if(hotPlugDevice)
{
switch (hotPlugEvent)
{
case AAPLHotPlugEventDeviceAdded:
[self handleMTLDeviceAddedNotification:hotPlugDevice];
break;
case AAPLHotPlugEventDeviceEjected:
case AAPLHotPlugEventDevicePulled:
[self handleMTLDeviceRemovalNotification:hotPlugDevice];
break;
}
}
}
当代表外部GPU的设备添加到系统时,handlePossibleHotPlugEvent
方法将设备添加到_supportedDevices
数组并为设备初始化新的AAPLRenderer
。 从系统中删除此类设备时,同样的方法会从_supportedDevices
数组中删除该设备并销毁其关联的AAPLRenderer
。 如果删除的设备用于渲染,则示例将切换到另一个设备和渲染器。
Update Per-Frame State and Data - 更新每帧状态和数据
MetalKit
为示例调用drawInMTKView:
方法来渲染每个帧。 在此方法中,示例调用handlePossibleHotPlugEvent
方法来处理主线程上的设备添加或删除。 此类操作包括更新与这些设备事件相关的UI以及完成必须在单个线程上以原子方式执行的任何其他状态更改。
然后,该示例调用drawFrameNumber:toView:
开始为当前渲染器渲染新帧。 为了确保能够在不同渲染器之间无缝切换的连续渲染,示例存储与渲染器本身分离的任何非渲染状态。 然后,对于每个帧,示例将任何必要的非渲染状态传递给特定的AAPLRenderer
实例。 在这种情况下,样本将当前帧编号_frameNumber
传递给渲染器,以便它可以计算样本3D模型的位置和旋转。
Deregister from Notifications - 从通知中取消注册
视图消失后,示例会显式取消注册以前的任何显示或设备通知。 否则,系统的通知中心和Metal
无法释放示例的视图控制器。
- (void)viewDidDisappear
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSApplicationDidChangeScreenParametersNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidChangeScreenNotification
object:nil];
MTLRemoveDeviceObserver(_metalDeviceObserver);
}
注意:示例不能将注销过程推迟到视图控制器的
dealloc
方法。 执行dealloc
方法时,系统的通知中心和Metal
仍然具有对视图控制器的引用,以防止它被销毁。
后记
本篇主要讲述了图形渲染的器件选择,感兴趣的给个赞或者关注~~~~