您好,欢迎访问代码之道!登录后台查看权限
  • 欢迎大神光临
  • 有朋自远方来 不亦悦乎

Metal中文文档:缓存基础

Objective-C 老刘 2018-09-28 2526 次浏览 0个评论

演示如何用顶点缓冲管理大量的顶点。

概述

你好三角形这个例子中,你学会了如何用Metal渲染 基本几何体(Geometry)。

在这个例子中,你将学会如何使用顶点缓冲去提高你的渲染效率。尤其,你将学会使用顶点缓冲 区存储和加载多个方形数据。

"缓冲区基础效果图"

管理大量顶点数据

在你和三角形这个例子中,渲染3个顶点,每个顶点32字节,顶点数据共计96字节。通过调用 setVertexBytes:length:atIndex:函数方法发送少量的顶点数据。该方法访问GPU申请少量 的内存,并且可以在每一帧中无性能成本申请。

不像你好三角形这个例子,本例需要渲染2250个顶点,每个32字节,顶点数据共计72000字节。如此 数量的顶点数据应该被更有效地管理。实际上,Metal在顶点数据超过4千字节(4096字节)的情况下, 是不可以使用方法setVertexBytes:length:atIndex:的。而且,顶点数据不应该在每一帧中 都重新分配和拷贝一次。

通常,Metal应用和游戏会绘制上千顶点的模型,每个顶点有多个属性,这要消耗几Mb内存。 为了更好和更高效地管理这类应用和游戏的数据,Metal提供了一个专门的数据容器MTLBuffer。 这些缓冲对象分配的内存能够被GPU访问,可以用于存放各种类型的自定义数据,尽管, 我们一般用于存放顶点数据。这个例子一次性分配了大量顶点数据,并拷贝到一个MTLBuffer对象, 这样就可以在每一帧重复使用了。

分配,生成并复制顶点数据

在Objective-C,字节缓冲用NSData或者NSMutableData对象封装,他们是安全的,并便于使用。 例子中,每个顶点的数据类型是AAPLVertex,并且每个方形由6个这样的顶点组成(每个方形由2个三角形组成)。 方形组成的30 x 20网格,共3,600个顶点,占115,200字节内存,这就是本例中分配顶点的数量。

const AAPLVertex quadVertices[] =
{
    // Pixel positions, RGBA colors
    { { -20,   20 },    { 1, 0, 0, 1 } },
    { {  20,   20 },    { 0, 0, 1, 1 } },
    { { -20,  -20 },    { 0, 1, 0, 1 } },

    { {  20,  -20 },    { 1, 0, 0, 1 } },
    { { -20,  -20 },    { 0, 1, 0, 1 } },
    { {  20,   20 },    { 0, 0, 1, 1 } },
};
const NSUInteger NUM_COLUMNS = 25;
const NSUInteger NUM_ROWS = 15;
const NSUInteger NUM_VERTICES_PER_QUAD = sizeof(quadVertices) / sizeof(AAPLVertex);
const float QUAD_SPACING = 50.0;

NSUInteger dataSize = sizeof(quadVertices) * NUM_COLUMNS * NUM_ROWS;
NSMutableData *vertexData = [[NSMutableData alloc] initWithLength:dataSize];

通常,Metal应用或者游戏都是从一个模型文件中加载顶点的。模型加载代码的复杂性因不同模型而异, 但是最终顶点数据都是存储到Metal能处理的字节缓冲区。为了避免引入加载模型代码,本例使用方法 generateVertexData模拟了顶点数据的转换,她会在运行时生成简单的顶点数据。

NSDataMTLBuffer都可以存储自定义数据,这样意味着你的应用在读写操作时需要负责定义(内存布局)和解释数据。 在这个例子中顶点数据是只读的,内存布局用数据类型AAPLVertex定义,这是顶点函数vertexShader所需的。

vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]],
             device AAPLVertex *vertices [[ buffer(AAPLVertexInputIndexVertices) ]],
             constant vector_uint2 *viewportSizePointer  [[ buffer(AAPLVertexInputIndexViewportSize) ]])

从根本上来说,NSDataMTLBuffer是非常相似的。但是 MTLBuffer对象是一个能够被GPU访问的容器, 使渲染管线能从中读取顶点数据。

NSData *vertexData = [AAPLRenderer generateVertexData];

// Create a vertex buffer by allocating storage that can be read by the GPU
_vertexBuffer = [_device newBufferWithLength:vertexData.length
                                     options:MTLResourceStorageModeShared];

// Copy the vertex data into the vertex buffer by accessing a pointer via
// the buffer's `contents` property
memcpy(_vertexBuffer.contents, vertexData.bytes, vertexData.length);

首先用方法newBufferWithLength:options:创建一个具有字节大小和访问选项的MTLBuffer新对象。 顶点数据占115,200字节内存(vertexData.length)并由CPU写入再由GPU读取(MTLResourceStorageModeShared)。

然后,使用函数memcpy()从源NSData对象拷贝到目标MTLBuffer对象。_vertexBuffer.contents查询 会返回一个CPU可访问的指向buffer内存的指针。通过源数据指针(vertexData.bytes)拷贝顶点数据到目标, 拷贝的数量为vertexData.length

设置并绘制顶点数据

因为例子的顶点数据现在已经存储到MTLBuffer对象了, 方法setVertexBytes:length:atIndex: 就不应该调用了;调用方法setVertexBuffer:offset:atIndex:代替。这个方法携带一个顶点缓冲对象、 一个顶点数据在顶点缓冲中的字节偏移量和一个映射到顶点函数的索引。

Because the sample's vertex data is now stored in a MTLBuffer object, the setVertexBytes:length:atIndex: method can no longer be called; the setVertexBuffer:offset:atIndex: method is called instead. This method takes as parameters a vertex buffer, a byte offset to the vertex data in that buffer, and an index that maps the buffer to the vertex function.

  • 注意: 使用了 MTLBuffer作为顶点函数参数,并不妨碍应用或者游戏使用方法setVertexBytes:length:atIndex: 设置其他的参数。事实上,这个例子沿用了你好三角形中的参数viewportSizePointer

最后,通过发起绘制调用对所有顶点进行绘制,从数组的第一个顶点(0)开始, 以数组的最后一个顶点结束(_numVertices)。

[renderEncoder setVertexBuffer:_vertexBuffer
                        offset:0
                       atIndex:AAPLVertexInputIndexVertices];

[renderEncoder setVertexBytes:&_viewportSize
                       length:sizeof(_viewportSize)
                      atIndex:AAPLVertexInputIndexViewportSize];

// Draw the vertices of the quads
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                  vertexStart:0
                  vertexCount:_numVertices];

下一步

在这个例子中,你学会如何用顶点缓冲提供你的渲染效率。 在[纹理基础]这个例子中,你将学会如何加载图片数据并渲染纹理到四边形中。


英文原文"Basic Buffers"

已有 2526 位网友参与,快来吐槽:

发表评论