美文网首页
Metal框架详细解析(四十七) —— Metal编程指南之资源

Metal框架详细解析(四十七) —— Metal编程指南之资源

作者: 刀客传奇 | 来源:发表于2018-11-11 17:37 被阅读248次

版本记录

版本号 时间
V1.0 2018.11.11 星期日

前言

很多做视频和图像的,相信对这个框架都不是很陌生,它渲染高级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框架详细解析(十) —— 基本组件(一)
11. Metal框架详细解析(十一) —— 基本组件之器件选择 - 图形渲染的器件选择(二)
12. Metal框架详细解析(十二) —— 基本组件之器件选择 - 计算处理的设备选择(三)
13. Metal框架详细解析(十三) —— 计算处理(一)
14. Metal框架详细解析(十四) —— 计算处理之你好,计算(二)
15. Metal框架详细解析(十五) —— 计算处理之关于线程和线程组(三)
16. Metal框架详细解析(十六) —— 计算处理之计算线程组和网格大小(四)
17. Metal框架详细解析(十七) —— 工具、分析和调试(一)
18. Metal框架详细解析(十八) —— 工具、分析和调试之Metal GPU Capture(二)
19. Metal框架详细解析(十九) —— 工具、分析和调试之GPU活动监视器(三)
20. Metal框架详细解析(二十) —— 工具、分析和调试之关于Metal着色语言文件名扩展名、使用Metal的命令行工具构建库和标记Metal对象和命令(四)
21. Metal框架详细解析(二十一) —— 基本课程之基本缓冲区(一)
22. Metal框架详细解析(二十二) —— 基本课程之基本纹理(二)
23. Metal框架详细解析(二十三) —— 基本课程之CPU和GPU同步(三)
24. Metal框架详细解析(二十四) —— 基本课程之参数缓冲 - 基本参数缓冲(四)
25. Metal框架详细解析(二十五) —— 基本课程之参数缓冲 - 带有数组和资源堆的参数缓冲区(五)
26. Metal框架详细解析(二十六) —— 基本课程之参数缓冲 - 具有GPU编码的参数缓冲区(六)
27. Metal框架详细解析(二十七) —— 高级技术之图层选择的反射(一)
28. Metal框架详细解析(二十八) —— 高级技术之使用专用函数的LOD(一)
29. Metal框架详细解析(二十九) —— 高级技术之具有参数缓冲区的动态地形(一)
30. Metal框架详细解析(三十) —— 延迟照明(一)
31. Metal框架详细解析(三十一) —— 在视图中混合Metal和OpenGL渲染(一)
32. Metal框架详细解析(三十二) —— Metal渲染管道教程(一)
33. Metal框架详细解析(三十三) —— Metal渲染管道教程(二)
34. Metal框架详细解析(三十四) —— Hello Metal! 一个简单的三角形的实现(一)
35. Metal框架详细解析(三十五) —— Hello Metal! 一个简单的三角形的实现(二)
36. Metal框架详细解析(三十六) —— Metal编程指南之概览(一)
37. Metal框架详细解析(三十七) —— Metal编程指南之基本Metal概念(二)
38. Metal框架详细解析(三十八) —— Metal编程指南之命令组织和执行模型(三)
39. Metal框架详细解析(三十九) —— Metal编程指南之资源对象:缓冲区和纹理(四)
40. Metal框架详细解析(四十) —— Metal编程指南之函数和库(五)
41. Metal框架详细解析(四十一) —— Metal编程指南之图形渲染:渲染命令编码器之Part 1(六)
42. Metal框架详细解析(四十二) —— Metal编程指南之图形渲染:渲染命令编码器之Part 2(七)
43. Metal框架详细解析(四十三) —— Metal编程指南之数据并行计算处理:计算命令编码器(八)
44. Metal框架详细解析(四十四) —— Metal编程指南之缓冲和纹理操作:Blit命令编码器(九)
45. Metal框架详细解析(四十五) —— Metal编程指南之Metal工具(十)
46. Metal框架详细解析(四十六) —— Metal编程指南之Tessellation(十一)

Resource Heaps - 资源堆

可用于:iOS_GPUFamily1_v3,iOS_GPUFamily2_v3,iOS_GPUFamily3_v2,tvOS_GPUFamily1_v2

资源堆允许Metal资源由相同的内存分配支持。这些资源是从称为堆的内存池创建的,它们由捕获和管理GPU工作依赖关系的fence进行跟踪。资源堆可帮助您的应用降低以下成本:

  • Resource creation - 资源创造。资源创建可能涉及分配新内存,将其映射到您的进程,并将其填充为零。通过从较大的堆或由堆支持的可循环资源内存创建资源,可以降低此成本。
  • Fixed memory budget - 固定内存预算。如果您的某些资源在一段时间内未使用,则虚拟内存可能会压缩资源内存以节省空间。这可能会导致额外的时间花费在再次使用此资源内存以供下次使用。通过使用少量堆,您可以将分配保持在给定的内存预算内,并确保这些资源不断使用(这有助于提供更一致的性能)。
  • Transient resources - 瞬态资源。为每个帧生成和消耗瞬态资源,但并非所有这些资源同时一起使用。为了减少内存消耗,未一起使用的瞬态资源可以共享由堆支持的相同内存。

Heaps - 堆

MTLHeap对象是表示抽象内存池的Metal资源。从此堆创建的资源定义为可别的或不可别的(aliasable or non-aliasable)。当子分配的资源与另一个别名资源共享相同的堆内存部分时,它们会被别名化。

1. Creating a Heap - 创建堆

通过调用MTLDevice对象的newHeapWithDescriptor:方法来创建MTLHeap对象。 MTLHeapDescriptor对象描述堆的存储模式,CPU缓存模式和字节大小。从同一堆子分配的所有资源共享相同的存储模式和CPU缓存模式。堆的字节大小必须足够大,以便为其资源分配足够的内存。

通过调用setPurgeableState:方法创建堆后,可以使堆可以清除。堆清除状态指的是其整个后备内存,并影响堆中的所有资源。堆是可以清除的,但它们的资源不是;子分配的资源仅反映堆的可清除状态。可清除性对于仅存储渲染目标的堆可能是有用的。

2. Sub-Allocating Heap Resources - 子分配堆资源

MTLBufferMTLTexture对象都可以从堆中进行子分配。为此,请调用MTLHeap对象的以下两种方法之一:

默认情况下,每个子分配的资源都被定义为不可别名的,这可以防止将来的子分配资源使用其内存。要使子分配的资源可以别名,请调用makeAliasable方法;这允许未来的子分配资源重用其内存。

别名不会破坏可分配的子分配资源,命令编码器仍可使用它们。这些资源拥有对其堆的强引用,只有在资源本身被销毁时才释放,而不是在它变为可别名的时候释放。只有在引用它们的所有命令缓冲区都已完成执行后,才能销毁子分配的资源。

注意:堆是线程安全的,但您可能仍需要在应用级别同步堆,以确保按预期设置别名。子分配资源之间的命令依赖关系不是自动的;必须通过Fences部分中描述的MTLFence API显式声明和管理手动跟踪。

Listing 13-1显示了使用堆进行简单的资源子分配

Listing 13-1  Simple heap creation and resource sub-allocation

// Calculate the size and alignment of each resource
MTLSizeAndAlign albedoSizeAndAlign = [_device heapTextureSizeAndAlignWithTextureDescriptor:_albedoDescriptor];
MTLSizeAndAlign normalSizeAndAlign = [_device heapTextureSizeAndAlignWithTextureDescriptor:_normalDescriptor];
MTLSizeAndAlign glossSizeAndAlign  = [_device heapTextureSizeAndAlignWithTextureDescriptor:_glossDescriptor];
 
// Calculate a heap size that satisfies the size requirements of all three resources
NSUInteger heapSize = albedoSizeAndAlign.size + normalSizeAndAlign.size + glossSizeAndAlign.size;
 
// Create a heap descriptor
MTLHeapDescriptor* heapDescriptor = [MTLHeapDescriptor new];
heapDescriptor.cpuCacheMode = MTLCPUCacheModeDefaultCache;
heapDescriptor.storageMode = MTLStorageModePrivate;
heapDescriptor.size = heapSize;
 
// Create a heap
id <MTLHeap> heap = [_device newHeapWithDescriptor:heapDescriptor];
 
// Create sub-allocated resources from the heap
id <MTLTexture> albedoTexture = [_heap newTextureWithDescriptor:_albedoDescriptor];
id <MTLTexture> normalTexture = [_heap newTextureWithDescriptor:_normalDescriptor];
id <MTLTexture> glossTexture  = [_heap newTextureWithDescriptor:_glossDescriptor];

Fences

MTLFence对象用于跨命令编码器跟踪和管理子分配的资源依赖性。资源依赖性随着资源由不同命令生成和使用而出现,无论这些命令是编码到同一队列还是不同队列。fence捕获GPU工作到特定时间点;当GPU遇到fence时,它必须等到所有捕获的工作完成后再继续执行。

1. Creating a Fence - 创建一个栅栏

通过调用MTLDevice对象的newFence方法创建MTLFence对象。fence主要用于跟踪目的,仅支持GPU内部跟踪,而不支持CPU和GPU之间的跟踪。 MTLFence协议不提供任何方法或完成处理程序,您只能修改label属性。

注意:Fences可以重复更新,硬件管理fence更新以防止死锁。

2. Tracking Fences Across Blit and Compute Command Encoders - 跟踪Blit和计算命令编码器的Fences

可以使用fence跟踪MTLBlitCommandEncoderMTLComputeCommandEncoder对象。要更新fence,请分别为每个命令编码器调用updateFence:updateFence:方法。要等待fence,请分别为每个命令编码器调用waitForFence:waitForFence:方法。

当命令缓冲区实际提交给硬件时,会更新或评估fence。这可以维护全局秩序并防止死锁。

驱动程序可能会在命令编码器的开头等待fence,驱动程序可能会延迟fence更新,直到命令编码器结束。因此,不允许首先更新然后在同一命令编码器中等待相同的栅栏(但是,您可以先等待然后更新)。生产者 - 消费者关系必须分散在不同的命令编码器中。

3. Tracking Fences Across Render Command Encoders - 跟踪渲染命令编码器中的Fences

可以使用更精细的粒度跟踪MTLRenderCommandEncoder对象。 MTLRenderStages枚举允许您指定更新或等待栅栏的渲染阶段,允许顶点和片段命令重叠执行。调用updateFence:afterStages:方法来更新fence并调用waitForFence:beforeStages:方法来等待fence。

4. Fence Examples - Fence示例

Listing 13-2显示了使用fence进行简单跟踪。

Listing 13-2  Simple fence tracking

id <MTLFence> fence = [_device newFence];
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
 
// Producer
id <MTLRenderCommandEncoder> renderCommandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:_descriptor];
/* Draw using resources associated with 'fence' */
[renderCommandEncoder updateFence:fence afterStages:MTLRenderStageFragment];
[renderCommandEncoder endEncoding];
 
// Consumer
id <MTLComputeCommandEncoder> computeCommandEncoder = [commandBuffer computeCommandEncoder];
[computeCommandEncoder waitForFence:fence];
/* Dispatch using resources associated with 'fence' */
[computeCommandEncoder endEncoding];
 
[commandBuffer commit];

如果只有后一个命令编码器更新fence,则不能假设两个命令编码器将完成。 消费者命令编码器必须明确等待将在围栏上发生冲突的所有命令编码器。 (GPU可能会开始执行尽可能多的命令,除非它遇到围栅。)Listing 13-3显示了引入竞争条件的围栏的错误使用。

Listing 13-3  Incorrect fence tracking

id <MTLFence> fence = [_device newFence];
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
 
// Producer 1
id <MTLRenderCommandEncoder> producerCommandEncoder1 = [commandBuffer renderCommandEncoderWithDescriptor:_descriptor];
/* Draw using resources associated with 'fence' */
[producerCommandEncoder1 endEncoding];
 
// Producer 2
id <MTLComputeCommandEncoder> producerCommandEncoder2 = [commandBuffer computeCommandEncoder];
/* Encode */
[producerCommandEncoder2 updateFence:fence];
[producerCommandEncoder2 endEncoding];
 
// Race condition at consumption!
// producerCommandEncoder2 updated the fence and will have completed its work
// producerCommandEncoder1 did not update the fence and therefore there is no guarantee that it will have completed its work
// Consumer
id <MTLComputeCommandEncoder> computeCommandEncoder = [commandBuffer computeCommandEncoder];
[computeCommandEncoder waitForFence:fence];
/* Dispatch using resources associated with 'fence' */
[computeCommandEncoder endEncoding];
 
[commandBuffer commit];

您仍负责对命令缓冲区提交队列进行排序,如Listing 13-4所示。 但是,fences不允许您控制队列间命令缓冲区排序

Listing 13-4  Sequencing fences across command buffer submission queues

id <MTLFence> fence = [_device newFence];
id <MTLCommandBuffer> commandBuffer0 = [_commandQueue0 commandBuffer];
id <MTLCommandBuffer> commandBuffer1 = [_commandQueue1 commandBuffer];
 
// Producer
id <MTLRenderCommandEncoder> renderCommandEncoder = [commandBuffer0 renderCommandEncoderWithDescriptor:_descriptor];
/* Draw using resources associated with 'fence' */
[renderCommandEncoder updateFence:fence afterStages:MTLRenderStageFragment];
[renderCommandEncoder endEncoding];
 
// Consumer
id <MTLComputeCommandEncoder> computeCommandEncoder = [commandBuffer1 computeCommandEncoder];
[computeCommandEncoder waitForFence:fence];
/* Dispatch using resources associated with 'fence' */
[computeCommandEncoder endEncoding];
 
// Ensure 'commandBuffer0' is scheduled before 'commandBuffer1'
[commandBuffer0 addScheduledHandler:^(id <MTLCommandBuffer>) {
    [commandBuffer1 commit];
}];
[commandBuffer0 commit];

Best Practices - 最佳实践

1. Separate Heaps for Render Target Types - 为渲染目标类型分配单独堆

有些设备不能随意对子分配资源进行别名;例如,可压缩深度纹理和MSAA纹理。您应该为每种类型的渲染目标创建不同的堆:颜色,深度,模板和MSAA。

2. Separate Heaps for Aliasable and Non-Aliasable Resources - 为可别名和不可别名的资源分配堆

在使子分配的资源可以别名时,必须假定此资源将对所有将来的堆子分配进行别名。如果以后分配不可别名的资源(例如寿命较长的纹理),那么这些资源可能会对您的临时资源进行别名,并且很难正确跟踪。

如果保留至少两个资源堆,则可以非常容易地跟踪哪些可别名和哪些不可别名:一个用于可别名资源(例如,渲染目标),另一个用于非别名资源(例如,资产纹理或顶点缓冲区) )。

3. Separate Heaps to Reduce Fragmentation - 分开堆以减少碎片

创建或删除许多不同大小的子分配资源可能会破坏内存。碎片整理要求您从碎片堆显式复制到另一个堆。或者,您可以创建专用于类似大小的子分配资源的多个堆。

堆也可以用于栈。当用作栈时,不会发生碎片。

4. Minimize Fencing - 最小化Fencing

细粒度的栅栏很难管理,它们会降低堆的跟踪效益。避免为每个子分配资源使用围栏;相反,使用单个围栅来跟踪具有相同同步要求的所有子分配资源。

5. Consider Tracking Non-Heap Resources - 考虑跟踪非堆资源

手动数据危险跟踪扩展到直接从MTLDevice对象创建的资源。在创建资源时指定新的MTLResourceHazardTrackingModeUntracked资源选项,然后使用fence跟踪它。手动跟踪可以减少许多只读资源的自动跟踪开销。


Sample Code - 示例代码

有关如何使用堆和栅栏的示例,请参阅MetalHeapsAndFences示例。

后记

本篇主要讲述了Metal编程指南之资源堆,感兴趣的给个赞或者关注~~~

相关文章

网友评论

      本文标题:Metal框架详细解析(四十七) —— Metal编程指南之资源

      本文链接:https://www.haomeiwen.com/subject/cubxfqtx.html