本系列文章是对 metalkit.org 上面MetalKit内容的全面翻译和学习.

MetalKit系统文章目录


今天我们关注一下使用GPU时的内存管理.Metal框架将内存资源定义为MTLBuffer对象,它是分配的无类型,无格式的内存(任何数据类型),MTLTexture对象则是分配的格式化内存来保存图片数据.我们在本文中只关注缓冲器.

创建MTLBuffer对象时有三种选项:

  • makeBuffer(length:options:) 创建一个MTLBuffer对象并分配一块新内存.
  • makeBuffer(bytes:length:options:) 从一片已经存在的区域复制数据到一块新分配的内存.
  • makeBuffer(bytesNoCopy:length:options:deallocator:) 重用一块已经存在的内存.

让我们创建一组缓冲器,看看数据是如何被传递到GPU的,及如何回传给CPU.我们首先创建一块缓冲器给输入和输出数据,并给它们初始化一些值:

let count = 1500
var myVector = [Float](repeating: 0, count: count)
var length = count * MemoryLayout< Float >.stride
var outBuffer = device.makeBuffer(bytes: myVector, length: length, options: [])
for (index, value) in myVector.enumerated() { myVector[index] = Float(index) }
var inBuffer = device.makeBuffer(bytes: myVector, length: length, options: [])
复制代码

新的MemoryLayout< Type >.stride语法在Swift 3被引入,来替代老的strideof(Type)函数.同时,因为内存排列的原因我们用.stride替代了.size.stride是指针增长时移动的字节数.下一步是把我们缓冲器告诉命令编码器:

encoder.setBuffer(inBuffer, offset: 0, at: 0)
encoder.setBuffer(outBuffer, offset: 0, at: 1)
复制代码

注意: <Metal最佳实践指南>指出当我们的数据小于4KB(例如一个千位的浮点数)时就避免创建缓冲器.在本例中我们应该使用setBytes()函数来代替创建缓冲器.

最后一步是读取GPU通过contents() 函数返回的数据,绑定内存数据到我们的输出缓冲器上:

let result = outBuffer.contents().bindMemory(to: Float.self, capacity: count)
var data = [Float](repeating:0, count: count)
for i in 0 ..< count { data[i] = result[i] }
复制代码

Metal资源必须被配置好,以便快速内存访问和驱动器性能优化.资源的储存模式允许我们定义缓冲器和纹理的储存位置和访问权限.如果你再看一眼上面我们创建缓冲器的地方,我们使用了默认([])的储存模式.

所有的iOStvOS设备支持unified memory model统一内存模型,它可以让CPUGPU共享系统内存,而macOS设备支持discrete memory model离散内存模型GPU拥有自己的内存.在iOStvOS中,Shared模式(MTLStorageModeShared)定义了系统内存可以被CPUGPU访问,而Private模式(MTLStorageModePrivate)定义系统内存只能被GPU访问.Shared模式是所有三种操作系统中的默认储存模式.

除了这两种储存模式外,macOS还有一种Managed模式(MTLStorageModeManaged),它为一种资源定义了一对同步内存,一个副本在系统内存中,另一个在视频内存中来获得更快的CPU和GPU本地访问.

现在让我们看看当我们将数据缓冲器发送给GPU时,GPU上面发生了什么.下面是个典型的顶点着色器例子:

vertex Vertices vertex_func(const device Vertices *vertices [[buffer(0)]], constant Uniforms &uniforms [[buffer(1)]], uint vid [[vertex_id]])
{...
}
复制代码

Metal Shading Language实现了地址空间修饰词来指定当函数变量或参数分配时的内存区域:

  • device - 指缓冲器内存对象,从设备内存池中分配,既可读又可写除非前面有const关键词就是只读的.
  • constant - 指缓冲器内存对象,从设备内存池中分配,但是是read-only只读的.程序作用域内的变量必须被声明为常量地址空间,并在声明语句中被初始化.常量地址空间为多个实例在执行图形或内核函数时访问缓冲器中的同一块位置的做了优化.
  • threadgroup - 仅用来分配内核函数中使用的变量,它们是为每个执行内核的线程组分配的,被线程组内的所有线程共享,只在执行内核的线程组的生命周期内才存在.
  • thread - 指每个线程的内存地址空间.分配在这个地址空间的变量对其它线程是不可见的.在图形或内核函数中声明的变量是分配在线程地址空间的.

作为奖励,让我们也看一下在Swift 3中另一种访问内存位置的方法.这段代码是从前面的文章The Model I/O framework中摘抄的,所以我们就不再讲解体素的细节了.只要想着我们需要遍历一个数组来获取值:

let url = Bundle.main.url(forResource: "teapot", withExtension: "obj")
let asset = MDLAsset(url: url)
let voxelArray = MDLVoxelArray(asset: asset, divisions: 10, patchRadius: 0)
if let data = voxelArray.voxelIndices() {data.withUnsafeBytes { (voxels: UnsafePointer<MDLVoxelIndex>) -> Void inlet count = data.count / MemoryLayout<MDLVoxelIndex>.sizelet position = voxelArray.spatialLocation(ofIndex: voxels.pointee)print(position)}
}
复制代码

在本例中,MDLVoxelArray对象有了个名为spatialLocation()的函数,它让我们用一个MDLVoxelIndex类型的UnsafePointer指针来遍历数组,并通过每个位置的pointee来访问数据.在本例中,我们只打印出地址中的第一个值,但一个简单的循环可以让我们得到所有的数,像这样:

var voxelIndex = voxels
for _ in 0..<count {let position = voxelArray.spatialLocation(ofIndex: voxelIndex.pointee)print(position)voxelIndex = voxelIndex.successor()
}
复制代码

源代码source code已发布在Github上.

下次见!

[MetalKit]34-Working-with-memory-in-Metal内存管理相关推荐

  1. Linux学习之系统编程篇:MMU(Memory Manager Unit 内存管理单元)

    一.虚拟内存地址 对应于上图的两端,其中 0 - 3G 是用户区 ,3 - 4G 是内核区.编码的内存地址都是虚拟地址. 在3G到4G之间是PCB 进程控制块.从3G到0依次为: (1)命令行参数 和 ...

  2. [iOS]Advanced Memory Management Programming Guide 高级内存管理编程指南(官方文档翻译)

    Advanced Memory Management Programming Guide - 高级内存管理编程指南(官方文档翻译) 版权声明:本文为博主原创翻译,如需转载请注明出处. 新博客文章地址: ...

  3. 【OC语法快览】四、基础内存管理

    Basic Memory Management                                                           基础内存管理 If you're w ...

  4. iOS内存管理策略和实践

    来源:http://www.baidu.com/link?url=irojqCBbZKsY7b0L2EBPkuEkfJ9MQvUf8kuNWQUXkBLk5b22Jl5rjozKaJS3n78jCnS ...

  5. Java程序员需要掌握的计算机底层知识(四):内存管理

    内存管理 单进程DOS时代 DOS时代 - 同一时间只能有一个进程在运行(也有一些特殊算法可以支持多进程) windows9x - 多个进程装入内存存在的问题: 内存不够用 互相打扰 为了解决这两个问 ...

  6. Linux 内存管理与系统架构设计

    Linux 提供各种模式(比如,消息队列),但是最著名的是 POSIX 共享内存(shmem,shared memory). Linux provides a variety of schemes ( ...

  7. Linux内存管理:MMU那些事儿

    <ARM SMMU原理与IOMMU技术("VT-d" DMA.I/O虚拟化.内存虚拟化)> <Linux内存管理:分页机制> <Linux内存管理:内 ...

  8. 操作系统:内存管理(概念)

    对于计算机系统而言,操作系统充当着基石的作用,它是连接计算机底层硬件与上层应用软件的桥梁,控制其他程序的运行,并且管理系统相关资源,同时提供配套的系统软件支持.对于专业的程序员而言,掌握一定的操作系统 ...

  9. 【操作系统】30天自制操作系统--(8)内存管理

    搞定了鼠标和键盘这两个外设的处理,终于走到了内存管理这一步.平时写上层应用的时候,就是malloc.free.memset这几件套,较少关注内存多少.内存能不能用等底层细节,但是要制作操作系统,对于内 ...

  10. 操作系统——内存管理

    摘要 主要是讲解操作系统的内存的管理技术和虚拟内存技术的实现原理. 内存管理基本的概念 内存也可称为主存,不管硬盘多大.里面存放了多少程序和数据,只要程序运行或者数据要进行计算处理,就必须先将它们装入 ...

最新文章

  1. 【VB】学生信息管理系统5——数据库代码
  2. java 判断页面刷新_如何判断一个网页是刷新还是关闭的方法
  3. Tomcat(一):背景知识和安装tomcat
  4. python进程数上限_python – 使用具有最大同时进程数的multipr...
  5. C++类的内存地址存放问题
  6. (BFS)Dungeon Master(poj2251)
  7. 华为P Smart Z海外上架:搭载弹出式前置摄像头
  8. 【贪心 哈夫曼树】bzoj2923: [Poi1998]The lightest language
  9. l455在线清零服务器,爱普生epson l455清零软件官方版
  10. 查看 IntelliJ IDEA 符号在插入符号上的定义
  11. web前端学习(三)——HTML5的字体、特殊符号、插入图片及头部元素的相关标签设置
  12. 从一个html页面传值到另一个页面,两个html之间的值传递(js location.search用法)
  13. Android 9.0 Toast源码改变引发的问题
  14. 华为鸿蒙系统小窗口,mate30pro升鸿蒙后小窗应用调不出来
  15. Lightning Network模拟器
  16. python 第五章 字典
  17. GitHub忘记用户名和密码如何找回
  18. Windows之内存映射文件
  19. windows10企业版开启RDP多用户同时登录
  20. 明明有空单元格,Ctrl+G定位空值报未找到单元格

热门文章

  1. MS Vs.net 2003 Sp1发布!
  2. UA PHYS515A 电磁理论III 静磁学问题3 静磁学问题的边界条件与标量势方法的应用
  3. 使用超图在网页上浏览地形
  4. codeproject网页翻译
  5. java 调用win32 api 学习总结
  6. 在Android中使用OpenGL ES开发第(五)节:GLSL基础语法
  7. 数据结构与算法(3)-----队列和栈
  8. ruby简单的基础 4
  9. MFC Timer定时器
  10. log4j的NDC/MDC区别与应用