版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.07 星期日 |
前言
很多做視頻和圖像的莹菱,相信對這個框架都不是很陌生碰镜,它渲染高級3D圖形盒件,并使用GPU執(zhí)行數(shù)據(jù)并行計算。接下來的幾篇我們就詳細的解析這個框架尼荆。感興趣的看下面幾篇文章。
1. Metal框架詳細解析(一)—— 基本概覽
2. Metal框架詳細解析(二) —— 器件和命令(一)
3. Metal框架詳細解析(三) —— 渲染簡單的2D三角形(一)
4. Metal框架詳細解析(四) —— 關(guān)于GPU Family 4(一)
5. Metal框架詳細解析(五) —— 關(guān)于GPU Family 4之關(guān)于Imageblocks(二)
6. Metal框架詳細解析(六) —— 關(guān)于GPU Family 4之關(guān)于Tile Shading(三)
7. Metal框架詳細解析(七) —— 關(guān)于GPU Family 4之關(guān)于光柵順序組(四)
8. Metal框架詳細解析(八) —— 關(guān)于GPU Family 4之關(guān)于增強的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細解析(九) —— 關(guān)于GPU Family 4之關(guān)于線程組共享(六)
10. Metal框架詳細解析(十) —— 基本組件(一)
Device Selection and Fallback for Graphics Rendering - 圖形渲染的器件選擇和后退
演示如何使用多個GPU并高效渲染到顯示器捣作。
本篇是macOS相關(guān)衣摩,要是看關(guān)于iOS的請忽略這篇文章。
Overview - 概覽
macOS
支持具有多個GPU和顯示的系統(tǒng)戈擒。 一個例子是MacBook Pro眶明,它具有低功耗集成GPU,高性能獨立GPU筐高,強大的外部GPU和其他顯示器搜囱。 Metal
應用必須仔細選擇能夠最大化特定顯示效率和性能的GPU。 他們還應該優(yōu)雅地響應任何GPU或顯示更改柑土,例如當用戶斷開外部GPU或在顯示器之間移動窗口時蜀肘。
Drawables, Displays, and GPUs
應用程序中的每個視圖都顯示在一個顯示器上,每個顯示器由一個GPU驅(qū)動冰单。 要在視圖中顯示圖形內(nèi)容幌缝,視圖的顯示將顯示來自顯示器驅(qū)動GPU的渲染圖形。
如果您的應用使用不會驅(qū)動視圖顯示的GPU進行渲染诫欠,則系統(tǒng)必須先將渲染GPU中的可繪制內(nèi)容復制到顯示GPU涵卵,然后才能顯示它。 這種傳輸可能很昂貴荒叼,因為GPU之間的帶寬受連接它們的總線的限制轿偎。 外部GPU的費用更高,因為他們的Thunderbolt 3
總線帶寬比內(nèi)部PCI Express
總線少得多被廓。
呈現(xiàn)drawable
的最快路徑是使用驅(qū)動視圖顯示的GPU渲染可繪制的路徑坏晦。 一個例子是帶有獨立GPU和集成GPU的MacBook Pro
,集成GPU可以在某些條件下驅(qū)動MacBook Pro
的顯示器(由熱狀態(tài),電池壽命或應用程序需求引起)昆婿。
另一個例子是Mac連接到外部GPU球碉,外部GPU驅(qū)動外部顯示器。
Transition Smoothly Between Devices - 在設(shè)備之間平滑轉(zhuǎn)換
示例的視圖控制器管理所有Metal
設(shè)備仓蛆,每個設(shè)備代表不同的GPU睁冬。當示例運行viewDidLoad
方法時,視圖控制器會為系統(tǒng)可用的每個設(shè)備初始化一個新的AAPLRenderer
看疙。該示例一次只使用一個設(shè)備豆拨,但它會為每個設(shè)備初始化一個渲染器,以便在所有設(shè)備上預加載和鏡像應用程序的Metal資源能庆。因此施禾,當應用程序在運行時在GPU之間切換時,樣本在設(shè)備之間平滑過渡搁胆,因為等效資源已經(jīng)可用并加載到每個設(shè)備上弥搞。這種預加載和鏡像策略避免了如果樣本在切換時需要加載資源則會出現(xiàn)的顯著延遲。
注意:預加載和鏡像資源允許您在設(shè)備之間平滑過渡渠旁,但它也會增加應用程序的總內(nèi)存使用量拓巧。您必須仔細確定應預先加載和鏡像哪些資源,以及只有當您的應用程序在設(shè)備之間切換時才應加載哪些資源一死。
Set the Optimal Device for the View’s Display - 為視圖的顯示設(shè)置最佳設(shè)備
視圖顯示后,示例將獲取顯示視圖的顯示的CGDirectDisplayID
值傻唾。 該示例使用此標識符來獲取驅(qū)動顯示的Metal
設(shè)備投慈。
// 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
設(shè)置此設(shè)備,并選擇與該設(shè)備關(guān)聯(lián)的AAPLRenderer
來執(zhí)行應用程序的渲染冠骄。 此設(shè)置可確保系統(tǒng)使用驅(qū)動顯示器的設(shè)備進行渲染伪煤,并避免將任何可繪制的數(shù)據(jù)從一個GPU復制到另一個GPU。
Handle Display Change Notifications - 處理顯示更改通知
為了跟上視圖顯示的最佳設(shè)備凛辣,樣本注冊了兩個系統(tǒng)通知:
NSApplicationDidChangeScreenParametersNotification
抱既。 當Mac的顯示配置發(fā)生變化時,系統(tǒng)會發(fā)布此通知扁誓。 例如防泵,用戶將外部顯示器與系統(tǒng)連接或斷開連接。 另一個例子是當驅(qū)動顯示器的GPU發(fā)生變化時蝗敢,例如當啟用自動圖形切換(Automatic Graphics Switching)
并且系統(tǒng)在離散和集成GPU之間切換以驅(qū)動顯示器時捷泞。NSWindowDidChangeScreenNotification
。 當任何窗口(包括包含應用程序視圖的窗口)移動到不同的顯示時寿谴,系統(tǒng)會發(fā)布此通知锁右。
// 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];
在這兩種情況下,系統(tǒng)都會調(diào)用示例的handleScreenChanges:
方法來處理通知。 然后咏瑟,樣本通過選擇與驅(qū)動顯示器的設(shè)備對應的AAPLRenderer
對象拂到,為視圖的顯示選擇最佳設(shè)備。
Set a GPU Eject Policy - 設(shè)置GPU彈出策略
默認情況下码泞,當應用程序使用的外部GPU從系統(tǒng)中刪除時兄旬,macOS會完全重新啟動應用程序。 應用通常通過以下方式處理重新啟動:
1) 當系統(tǒng)調(diào)用應用程序的應用程序的
application willEncodeRestorableState:
方法時浦夷,在macOS退出應用程序之前辖试,保存盡可能多的狀態(tài)。2) 在系統(tǒng)調(diào)用應用程序的
application:didDecodeRestorableState:
方法時劈狐,在macOS重新啟動應用程序之后罐孝,恢復任何已保存的狀態(tài)。
該示例避免了此應用程序重新啟動例程肥缔,而是選擇處理外部GPU移除本身莲兢,而不需要macOS
退出并重新啟動應用程序。 示例的Info.plist
文件具有帶有wait
值的GPUEjectPolicy
鍵续膳,表示應用程序?qū)⑼ㄟ^響應Metal發(fā)布的相應通知來顯式處理外部GPU的刪除改艇。
Register for External GPU Notifications - 注冊外部GPU通知
該示例調(diào)用MTLCopyAllDevicesWithObserver()
函數(shù)以獲取系統(tǒng)可用的所有Metal設(shè)備。 此方法允許樣本提供MTLDeviceNotificationHandler
塊坟岔,該塊在系統(tǒng)中添加或刪除外部GPU時執(zhí)行谒兄。 此處理程序提供兩個參數(shù):
-
device
。 添加或刪除的設(shè)備社付。 -
notifyName
承疲。 描述觸發(fā)通知的事件的值。
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通知
通知處理程序可以在任何線程上執(zhí)行鸥咖。 但是燕鸽,所有UI更新必須在主線程上進行,并且必須明確使應用程序的狀態(tài)更改成為線程安全的啼辣。 為了符合這些線程要求啊研,視圖控制器使用@synchronized
指令保護對_hotPlugEvent
和_hotPlugDevice
實例變量的訪問。 (@synchronized
指令是在Objective-C
代碼中創(chuàng)建互斥鎖的便捷方式鸥拧。)
當發(fā)生通知時党远,該示例在markHotPlugNotificationForDevice:name:
方法中設(shè)置這些實例變量。
- (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的設(shè)備添加到系統(tǒng)時,handlePossibleHotPlugEvent
方法將設(shè)備添加到_supportedDevices
數(shù)組并為設(shè)備初始化新的AAPLRenderer
舆声。 從系統(tǒng)中刪除此類設(shè)備時花沉,同樣的方法會從_supportedDevices
數(shù)組中刪除該設(shè)備并銷毀其關(guān)聯(lián)的AAPLRenderer
柳爽。 如果刪除的設(shè)備用于渲染,則示例將切換到另一個設(shè)備和渲染器碱屁。
Update Per-Frame State and Data - 更新每幀狀態(tài)和數(shù)據(jù)
MetalKit
為示例調(diào)用drawInMTKView:
方法來渲染每個幀磷脯。 在此方法中,示例調(diào)用handlePossibleHotPlugEvent
方法來處理主線程上的設(shè)備添加或刪除娩脾。 此類操作包括更新與這些設(shè)備事件相關(guān)的UI以及完成必須在單個線程上以原子方式執(zhí)行的任何其他狀態(tài)更改赵誓。
然后,該示例調(diào)用drawFrameNumber:toView:
開始為當前渲染器渲染新幀柿赊。 為了確保能夠在不同渲染器之間無縫切換的連續(xù)渲染俩功,示例存儲與渲染器本身分離的任何非渲染狀態(tài)。 然后碰声,對于每個幀诡蜓,示例將任何必要的非渲染狀態(tài)傳遞給特定的AAPLRenderer
實例。 在這種情況下胰挑,樣本將當前幀編號_frameNumber
傳遞給渲染器蔓罚,以便它可以計算樣本3D模型的位置和旋轉(zhuǎn)。
Deregister from Notifications - 從通知中取消注冊
視圖消失后瞻颂,示例會顯式取消注冊以前的任何顯示或設(shè)備通知豺谈。 否則,系統(tǒng)的通知中心和Metal
無法釋放示例的視圖控制器贡这。
- (void)viewDidDisappear
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSApplicationDidChangeScreenParametersNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidChangeScreenNotification
object:nil];
MTLRemoveDeviceObserver(_metalDeviceObserver);
}
注意:示例不能將注銷過程推遲到視圖控制器的
dealloc
方法茬末。 執(zhí)行dealloc
方法時,系統(tǒng)的通知中心和Metal
仍然具有對視圖控制器的引用盖矫,以防止它被銷毀团南。
后記
本篇主要講述了圖形渲染的器件選擇,感興趣的給個贊或者關(guān)注~~~~