Metal框架詳細(xì)解析(十一) —— 基本組件之器件選擇 - 圖形渲染的器件選擇(二)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.10.07 星期日

前言

很多做視頻和圖像的,相信對(duì)這個(gè)框架都不是很陌生,它渲染高級(jí)3D圖形,并使用GPU執(zhí)行數(shù)據(jù)并行計(jì)算。接下來(lái)的幾篇我們就詳細(xì)的解析這個(gè)框架。感興趣的看下面幾篇文章。
1. Metal框架詳細(xì)解析(一)—— 基本概覽
2. Metal框架詳細(xì)解析(二) —— 器件和命令(一)
3. Metal框架詳細(xì)解析(三) —— 渲染簡(jiǎn)單的2D三角形(一)
4. Metal框架詳細(xì)解析(四) —— 關(guān)于GPU Family 4(一)
5. Metal框架詳細(xì)解析(五) —— 關(guān)于GPU Family 4之關(guān)于Imageblocks(二)
6. Metal框架詳細(xì)解析(六) —— 關(guān)于GPU Family 4之關(guān)于Tile Shading(三)
7. Metal框架詳細(xì)解析(七) —— 關(guān)于GPU Family 4之關(guān)于光柵順序組(四)
8. Metal框架詳細(xì)解析(八) —— 關(guān)于GPU Family 4之關(guān)于增強(qiáng)的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細(xì)解析(九) —— 關(guān)于GPU Family 4之關(guān)于線程組共享(六)
10. Metal框架詳細(xì)解析(十) —— 基本組件(一)

Device Selection and Fallback for Graphics Rendering - 圖形渲染的器件選擇和后退

演示如何使用多個(gè)GPU并高效渲染到顯示器。

本篇是macOS相關(guān),要是看關(guān)于iOS的請(qǐng)忽略這篇文章。


Overview - 概覽

macOS支持具有多個(gè)GPU和顯示的系統(tǒng)。 一個(gè)例子是MacBook Pro,它具有低功耗集成GPU,高性能獨(dú)立GPU,強(qiáng)大的外部GPU和其他顯示器。 Metal應(yīng)用必須仔細(xì)選擇能夠最大化特定顯示效率和性能的GPU。 他們還應(yīng)該優(yōu)雅地響應(yīng)任何GPU或顯示更改,例如當(dāng)用戶斷開外部GPU或在顯示器之間移動(dòng)窗口時(shí)。


Drawables, Displays, and GPUs

應(yīng)用程序中的每個(gè)視圖都顯示在一個(gè)顯示器上,每個(gè)顯示器由一個(gè)GPU驅(qū)動(dòng)。 要在視圖中顯示圖形內(nèi)容,視圖的顯示將顯示來(lái)自顯示器驅(qū)動(dòng)GPU的渲染圖形。

如果您的應(yīng)用使用不會(huì)驅(qū)動(dòng)視圖顯示的GPU進(jìn)行渲染,則系統(tǒng)必須先將渲染GPU中的可繪制內(nèi)容復(fù)制到顯示GPU,然后才能顯示它。 這種傳輸可能很昂貴,因?yàn)镚PU之間的帶寬受連接它們的總線的限制。 外部GPU的費(fèi)用更高,因?yàn)樗麄兊?code>Thunderbolt 3總線帶寬比內(nèi)部PCI Express總線少得多。

呈現(xiàn)drawable的最快路徑是使用驅(qū)動(dòng)視圖顯示的GPU渲染可繪制的路徑。 一個(gè)例子是帶有獨(dú)立GPU和集成GPU的MacBook Pro,集成GPU可以在某些條件下驅(qū)動(dòng)MacBook Pro的顯示器(由熱狀態(tài),電池壽命或應(yīng)用程序需求引起)。

另一個(gè)例子是Mac連接到外部GPU,外部GPU驅(qū)動(dòng)外部顯示器。


Transition Smoothly Between Devices - 在設(shè)備之間平滑轉(zhuǎn)換

示例的視圖控制器管理所有Metal設(shè)備,每個(gè)設(shè)備代表不同的GPU。當(dāng)示例運(yùn)行viewDidLoad方法時(shí),視圖控制器會(huì)為系統(tǒng)可用的每個(gè)設(shè)備初始化一個(gè)新的AAPLRenderer。該示例一次只使用一個(gè)設(shè)備,但它會(huì)為每個(gè)設(shè)備初始化一個(gè)渲染器,以便在所有設(shè)備上預(yù)加載和鏡像應(yīng)用程序的Metal資源。因此,當(dāng)應(yīng)用程序在運(yùn)行時(shí)在GPU之間切換時(shí),樣本在設(shè)備之間平滑過(guò)渡,因?yàn)榈刃зY源已經(jīng)可用并加載到每個(gè)設(shè)備上。這種預(yù)加載和鏡像策略避免了如果樣本在切換時(shí)需要加載資源則會(huì)出現(xiàn)的顯著延遲。

注意:預(yù)加載和鏡像資源允許您在設(shè)備之間平滑過(guò)渡,但它也會(huì)增加應(yīng)用程序的總內(nèi)存使用量。您必須仔細(xì)確定應(yīng)預(yù)先加載和鏡像哪些資源,以及只有當(dāng)您的應(yīng)用程序在設(shè)備之間切換時(shí)才應(yīng)加載哪些資源。


Set the Optimal Device for the View’s Display - 為視圖的顯示設(shè)置最佳設(shè)備

視圖顯示后,示例將獲取顯示視圖的顯示的CGDirectDisplayID值。 該示例使用此標(biāo)識(shí)符來(lái)獲取驅(qū)動(dòng)顯示的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來(lái)執(zhí)行應(yīng)用程序的渲染。 此設(shè)置可確保系統(tǒng)使用驅(qū)動(dòng)顯示器的設(shè)備進(jìn)行渲染,并避免將任何可繪制的數(shù)據(jù)從一個(gè)GPU復(fù)制到另一個(gè)GPU。


Handle Display Change Notifications - 處理顯示更改通知

為了跟上視圖顯示的最佳設(shè)備,樣本注冊(cè)了兩個(gè)系統(tǒng)通知:

  • NSApplicationDidChangeScreenParametersNotification。 當(dāng)Mac的顯示配置發(fā)生變化時(shí),系統(tǒng)會(huì)發(fā)布此通知。 例如,用戶將外部顯示器與系統(tǒng)連接或斷開連接。 另一個(gè)例子是當(dāng)驅(qū)動(dòng)顯示器的GPU發(fā)生變化時(shí),例如當(dāng)啟用自動(dòng)圖形切換(Automatic Graphics Switching)并且系統(tǒng)在離散和集成GPU之間切換以驅(qū)動(dòng)顯示器時(shí)。

  • NSWindowDidChangeScreenNotification。 當(dāng)任何窗口(包括包含應(yīng)用程序視圖的窗口)移動(dòng)到不同的顯示時(shí),系統(tǒng)會(huì)發(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)都會(huì)調(diào)用示例的handleScreenChanges:方法來(lái)處理通知。 然后,樣本通過(guò)選擇與驅(qū)動(dòng)顯示器的設(shè)備對(duì)應(yīng)的AAPLRenderer對(duì)象,為視圖的顯示選擇最佳設(shè)備。


Set a GPU Eject Policy - 設(shè)置GPU彈出策略

默認(rèn)情況下,當(dāng)應(yīng)用程序使用的外部GPU從系統(tǒng)中刪除時(shí),macOS會(huì)完全重新啟動(dòng)應(yīng)用程序。 應(yīng)用通常通過(guò)以下方式處理重新啟動(dòng):

  • 1) 當(dāng)系統(tǒng)調(diào)用應(yīng)用程序的應(yīng)用程序的application willEncodeRestorableState:方法時(shí),在macOS退出應(yīng)用程序之前,保存盡可能多的狀態(tài)。

  • 2) 在系統(tǒng)調(diào)用應(yīng)用程序的application:didDecodeRestorableState:方法時(shí),在macOS重新啟動(dòng)應(yīng)用程序之后,恢復(fù)任何已保存的狀態(tài)。

該示例避免了此應(yīng)用程序重新啟動(dòng)例程,而是選擇處理外部GPU移除本身,而不需要macOS退出并重新啟動(dòng)應(yīng)用程序。 示例的Info.plist文件具有帶有wait值的GPUEjectPolicy鍵,表示應(yīng)用程序?qū)⑼ㄟ^(guò)響應(yīng)Metal發(fā)布的相應(yīng)通知來(lái)顯式處理外部GPU的刪除。


Register for External GPU Notifications - 注冊(cè)外部GPU通知

該示例調(diào)用MTLCopyAllDevicesWithObserver()函數(shù)以獲取系統(tǒng)可用的所有Metal設(shè)備。 此方法允許樣本提供MTLDeviceNotificationHandler塊,該塊在系統(tǒng)中添加或刪除外部GPU時(shí)執(zhí)行。 此處理程序提供兩個(gè)參數(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 - 響應(yīng)外部GPU通知

通知處理程序可以在任何線程上執(zhí)行。 但是,所有UI更新必須在主線程上進(jìn)行,并且必須明確使應(yīng)用程序的狀態(tài)更改成為線程安全的。 為了符合這些線程要求,視圖控制器使用@synchronized指令保護(hù)對(duì)_hotPlugEvent_hotPlugDevice實(shí)例變量的訪問(wèn)。 (@synchronized指令是在Objective-C代碼中創(chuàng)建互斥鎖的便捷方式。)

當(dāng)發(fā)生通知時(shí),該示例在markHotPlugNotificationForDevice:name:方法中設(shè)置這些實(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;
    }
}

該示例在主線程上讀取這些實(shí)例變量,并在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;
        }
    }
}

當(dāng)代表外部GPU的設(shè)備添加到系統(tǒng)時(shí),handlePossibleHotPlugEvent方法將設(shè)備添加到_supportedDevices數(shù)組并為設(shè)備初始化新的AAPLRenderer。 從系統(tǒng)中刪除此類設(shè)備時(shí),同樣的方法會(huì)從_supportedDevices數(shù)組中刪除該設(shè)備并銷毀其關(guān)聯(lián)的AAPLRenderer。 如果刪除的設(shè)備用于渲染,則示例將切換到另一個(gè)設(shè)備和渲染器。


Update Per-Frame State and Data - 更新每幀狀態(tài)和數(shù)據(jù)

MetalKit為示例調(diào)用drawInMTKView:方法來(lái)渲染每個(gè)幀。 在此方法中,示例調(diào)用handlePossibleHotPlugEvent方法來(lái)處理主線程上的設(shè)備添加或刪除。 此類操作包括更新與這些設(shè)備事件相關(guān)的UI以及完成必須在單個(gè)線程上以原子方式執(zhí)行的任何其他狀態(tài)更改。

然后,該示例調(diào)用drawFrameNumber:toView:開始為當(dāng)前渲染器渲染新幀。 為了確保能夠在不同渲染器之間無(wú)縫切換的連續(xù)渲染,示例存儲(chǔ)與渲染器本身分離的任何非渲染狀態(tài)。 然后,對(duì)于每個(gè)幀,示例將任何必要的非渲染狀態(tài)傳遞給特定的AAPLRenderer實(shí)例。 在這種情況下,樣本將當(dāng)前幀編號(hào)_frameNumber傳遞給渲染器,以便它可以計(jì)算樣本3D模型的位置和旋轉(zhuǎn)。


Deregister from Notifications - 從通知中取消注冊(cè)

視圖消失后,示例會(huì)顯式取消注冊(cè)以前的任何顯示或設(shè)備通知。 否則,系統(tǒng)的通知中心和Metal無(wú)法釋放示例的視圖控制器。

- (void)viewDidDisappear
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSApplicationDidChangeScreenParametersNotification
                                                  object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSWindowDidChangeScreenNotification
                                                  object:nil];

    MTLRemoveDeviceObserver(_metalDeviceObserver);
}

注意:示例不能將注銷過(guò)程推遲到視圖控制器的dealloc方法。 執(zhí)行dealloc方法時(shí),系統(tǒng)的通知中心和Metal仍然具有對(duì)視圖控制器的引用,以防止它被銷毀。

后記

本篇主要講述了圖形渲染的器件選擇,感興趣的給個(gè)贊或者關(guān)注~~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容