资源对象:缓冲区和纹理
本章介绍MTLResource用于存储未格式化存储器和格式化图像数据的
Metal资源对象(MTLResource)。有两种类型的MTLResource对象:
MTLBuffer表示可以包含任何类型数据的未格式化内存的分配。
缓冲区通常用于顶点,着色器和计算状态数据。MTLTexture表示具有指定纹理类型和像素格式的格式化图像数据的分配。纹理对象用作顶点,片段或计算函数的源纹理,以及存储图形渲染输出(即附件)。
MTLSamplerState本章还讨论了对象。虽然采样器本身不是资源,但是在使用纹理对象执行查找计算时会使用它们。
缓冲区是无类型的内存分配
一个MTLBuffer对象表示的存储器的分配,它可以包含任何类型的数据。
创建缓冲区对象
以下MTLDevice方法创建并返回一个MTLBuffer对象:
该newBufferWithLength:options:方法创建一个MTLBuffer新的存储分配对象。
该newBufferWithBytes:length:options:方法MTLBuffer通过将来自现有存储(位于CPU地址pointer)的数据复制到新的存储分配中来创建对象。
该newBufferWithBytesNoCopy:length:options:deallocator:方法创建MTLBuffer具有现有存储分配的对象,并且不会为此对象分配任何新存储。
所有缓冲区创建方法都有输入值length来指示存储分配的大小(以字节为单位)。所有的方法也接受一个MTLResourceOptions对象options,可以修改创建的缓冲区的行为。如果值为options0,则默认值用于资源选项。
缓冲方法
该MTLBuffer协议具有以下的方法:
该contents方法返回缓冲区的存储分配的CPU地址。
该newTextureWithDescriptor:offset:bytesPerRow:方法创建引用缓冲区数据的特殊类型的纹理对象。此方法在创建纹理对象中有详细说明。
纹理是格式化的图像数据
一个MTLTexture对象表示可被用作用于顶点着色器资源,片段着色器,或计算功能格式化的图像数据的分配,或者作为附件被用作渲染目的地。一个MTLTexture对象可以有一个以下结构:
1D,2D或3D图像
一维或二维图像阵列
一个立方体六个2D图像
MTLPixelFormat指定MTLTexture对象中各个像素的组织。像素格式在“ 像素格式”进一步讨论。
创建一个纹理对象
以下方法创建并返回一个MTLTexture对象:
用纹理图像数据创建一个新的存储分配对象的newTextureWithDescriptor:方法,使用对象来描述纹理的属性。MTLDevice MTLTextureMTLTextureDescriptor
所述newTextureViewWithPixelFormat:的方法MTLTexture创建一个
MTLTexture共享相同的存储分配作为调用对象MTLTexture的对象。由于它们共享相同的存储,所以对新的纹理对象的像素的任何改变都会反映在调用纹理对象中,反之亦然。对于新创建的纹理,该newTextureViewWithPixelFormat:方法重新解释调用MTLTexture
对象的存储分配的现有纹理图像数据,就像数据以指定的像素格式存储一样。在MTLPixelFormat新的纹理对象必须是兼容与MTLPixelFormat原始纹理的对象。(有关普通,打包和压缩像素格式的详细信息,请参阅纹理的像素格式。)所述newTextureWithDescriptor:offset:bytesPerRow:的方法MTLBuffer创建一个MTLTexture共享主叫的存储分配对象MTLBuffer对象作为其纹理图像数据。当它们共享相同的存储时,对新的纹理对象的像素的任何改变都反映在调用纹理对象中,反之亦然。在纹理和缓冲区之间共享存储可以阻止使用某些纹理优化,例如像素闪烁或平铺。
使用纹理描述符创建纹理对象
MTLTextureDescriptor定义用于创建MTLTexture对象的属性,包括其图像大小(宽度,高度和深度),像素格式,排列(阵列或多维数据集类型)和mipmap的数量。的MTLTextureDescriptor属性创建一个的过程中仅使用MTLTexture对象。创建MTLTexture对象后,其MTLTextureDescriptor对象中的属性更改对该纹理不再有任何影响。
从描述符创建一个或多个纹理:
1 :创建MTLTextureDescriptor包含描述纹理数据的纹理属性的自定义对象:
- 该textureType属性指定纹理的维度和排列(例如,数组或多维数据集)。
- the width,heightand和depthproperties指定基本级别纹理mipmap的每个维度中的像素大小。
- 该pixelFormat属性指定像素如何存储在纹理中。
- 该arrayLength属性指定一个MTLTextureType1DArray或MTLTextureType2DArray类型的纹理对象的数组元素的数量。
- 该mipmapLevelCount属性指定mipmap级别的数量。
- 该sampleCount属性指定每个像素中的样本数。
- 该resourceOptions属性指定其内存分配的行为。
2:MTLTextureDescriptor通过调用对象的newTextureWithDescriptor:方法从对象创建一个纹理MTLDevice。在纹理创建之后,调用replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:方法来加载纹理图像数据,如将纹理图像数据复制到纹理中所详述的。
3:要创建更多的MTLTexture对象,可以重用同一个MTLTextureDescriptor对象,根据需要修改描述符的属性值。
清单3-1显示了用于创建纹理描述符txDesc并为3D,64x64x64纹理设置其属性的代码。
清单3-1 使用自定义纹理描述符创建纹理对象
MTLTextureDescriptor * txDesc = [[MTLTextureDescriptor alloc] init];
txDesc.textureType = MTLTextureType3D;
txDesc.height = 64;
txDesc.width = 64;
txDesc.depth = 64;
txDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;
txDesc.arrayLength = 1;
txDesc.mipmapLevelCount = 1;
id <MTLTexture> aTexture = [device newTextureWithDescriptor:txDesc];
使用纹理片
一个slice是单个一维,二维或三维纹理图像和其所有相关的mipmap。对于每个切片:
由指定的基级的mipmap的大小width,height和depth所述的属性MTLTextureDescriptor的对象。
mipmap级别i的缩放大小由max(1,floor(width/ 2 i))x max(1,floor(height/ 2 i))x max(1,floor(depth/ 2 i))指定。最大mipmap级别是第一个实现大小为1 x 1 x 1的mipmap级别。
在一个切片的mipmap级的数目可以按楼层来确定(日志2(MAX( ,width,height)))+depth 1。
所有纹理对象至少有一个切片; 立方体和数组纹理类型可能有几个切片。在写入和读取纹理图像数据的方法中,将纹理图像数据复制到纹理和从纹理复制图像数据,slice是一个基于零的输入值。对于1D,2D或3D纹理,只有一个切片,因此值slice必须为0.立方体纹理具有六个总的2D切片,从0到5寻址。对于1DArray和2DArray纹理类型,每个数组元素代表一片。例如,对于具有arrayLength= 10 的2DArray纹理类型,有10个总片,从0到9寻址。要从整体纹理结构中选择单个1D,2D或3D图像,首先选择一个片段,然后选择该片段中的mipmap级别。
用方便的方法创建纹理描述符
对于常见的2D和多维数据集纹理,请使用以下便利方法创建一个MTLTextureDescriptor对象,其中的几个属性值自动设置:
该texture2DDescriptorWithPixelFormat:width:height:mipmapped:
方法MTLTextureDescriptor为2D纹理创建一个对象。的width和height值定义了2D纹理的尺寸。该type属性被自动设置为MTLTextureType2D,并且depth和arrayLength被设置为1。该textureCubeDescriptorWithPixelFormat:size:mipmapped:方法创建一个MTLTextureDescriptor用于立方织构,其中所述对象type属性设置为MTLTextureTypeCube,width和height被设置为大小,depth并且arrayLength被设置为1。
两种MTLTextureDescriptor方便的方法都接受一个输入值,pixelFormat它定义了纹理的像素格式。两种方法也接受输入值mipmapped,该值决定纹理图像是否被映射。(如果mipmapped是YES,纹理是mipmap。)
清单3-2使用该texture2DDescriptorWithPixelFormat:width:height:mipmapped:方法64x64为未映射的2D纹理创建描述符对象。
清单3-2 使用方便的纹理描述符创建纹理对象
MTLTextureDescriptor * texDesc = [MTLTextureDescriptor
texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
width:64 height:64 mipmapped:NO];
id <MTLTexture> myTexture = [device newTextureWithDescriptor:texDesc];
从纹理复制图像数据
要将图像数据同步复制到或从MTLTexture对象的存储分配中复制数据,请使用以下方法:
replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:将来自调用者指针的像素数据的区域复制到指定纹理片的存储分配的一部分中。replaceRegion:mipmapLevel:withBytes:bytesPerRow:是类似的便利方法,将像素数据区域复制到默认切片中,假设切片相关参数的默认值(即slice= 0和bytesPerImage= 0)。
getBytes:bytesPerRow:bytesPerImage:fromRegion:mipmapLevel:slice:从指定的纹理切片检索像素数据的区域。getBytes:bytesPerRow:fromRegion:mipmapLevel:是类似的便利方法,从默认切片中检索像素数据的区域,假设切片相关参数(slice= 0和bytesPerImage= 0)的默认值。
清单3-3显示了如何replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:从系统内存textureData中的slice 0和mipmap级别的源数据中调用指定纹理图像0。
清单3-3 将图像数据复制到纹理中
// pixelSize is the size of one pixel, in bytes
// width, height - number of pixels in each dimension
NSUInteger myRowBytes = width * pixelSize;
NSUInteger myImageBytes = rowBytes * height;
[tex replaceRegion:MTLRegionMake2D(0,0,width,height)
mipmapLevel:0 slice:0 withBytes:textureData
bytesPerRow:myRowBytes bytesPerImage:myImageBytes];
纹理的像素格式
MTLPixelFormat指定MTLTexture对象的各个像素中的颜色,深度和模板数据存储的组织。像素格式有三种:普通,打包,压缩。
普通格式只有常规的8位,16位或32位颜色组件。每个组件被布置在增加的存储器地址中,其中第一个列出的组件处于最低地址。例如,MTLPixelFormatRGBA8Unorm是一个32位格式,每个颜色分量有8位; 最低的地址包含红色,下一个地址包含绿色,依此类推。相比之下,对于MTLPixelFormatBGRA8Unorm,最低的地址包含蓝色,下一个地址包含绿色,依此类推。
打包格式将多个组件组合成一个16位或32位值,其中组件从最低位到最高有效位(LSB到MSB)存储。例如,MTLPixelFormatRGB10A2Uint是一个32位打包格式,由三个10位通道(用于R,G和B)和两位用于alpha。
压缩格式以像素块排列,每个块的布局特定于该像素格式。压缩像素格式只能用于2D,2D阵列或多维数据集纹理类型。压缩的格式不能用于创建1D,2DMultisample或3D纹理。
该MTLPixelFormatGBGR422和MTLPixelFormatBGRG422是被用于存储像素的YUV色彩空间特殊的像素格式。这些格式仅支持2D纹理(但不包括2D数组,也不是立方体类型),没有mipmap,甚至是偶数width。
几个像素格式存储具有sRGB颜色空间值的颜色组件(例如,MTLPixelFormatRGBA8Unorm_sRGB或MTLPixelFormatETC2_RGB8_sRGB)。当采样操作以sRGB像素格式引用纹理时,Metal实现在采样操作发生之前将sRGB颜色空间分量转换为线性颜色空间。从sRGB组件S到线性分量L的转换如下所示:
如果S <= 0.04045,L = S / 12.92
如果S> 0.04045,L =((S + 0.055)/1.055)^2.4
相反,当渲染到使用具有sRGB像素格式的纹理的可呈现颜色的附件时,实现将线性颜色值转换为sRGB,如下所示:
如果L <= 0.0031308,则S = L * 12.92
如果L> 0.0031308,S =(1.055 * L 0.41667)^-0.055
有关渲染的像素格式的更多信息,请参阅创建渲染通过描述符。
创建用于纹理查找的采样器状态对象
一个MTLSamplerState对象定义了图形或计算函数对MTLTexture对象执行纹理采样操作时使用的寻址,过滤和其他属性。采样器描述符定义采样器状态对象的属性。要创建采样器状态对象:
1: 调用对象的newSamplerStateWithDescriptor:方法MTLDevice来创建一个MTLSamplerDescriptor对象。
2:在MTLSamplerDescriptor对象中设置所需的值,包括过滤选项,寻址模式,最大各向异性和详细级参数。
3:MTLSamplerState通过调用创建描述符newSamplerStateWithDescriptor:的MTLDevice对象的方法,从采样器描述符创建一个对象。
您可以重用采样器描述符对象来创建更多的MTLSamplerState对象,根据需要修改描述符的属性值。描述符的属性仅在对象创建过程中使用。在创建采样器状态之后,更改其描述符中的属性将不再对该采样器状态产生影响。
清单3-4是一个代码示例,它创建MTLSamplerDescriptor并配置它以创建一个MTLSamplerState。非默认值为描述符对象的过滤器和地址模式属性设置。然后该newSamplerStateWithDescriptor:方法使用采样器描述符来创建采样器状态对象。
清单3-4 创建采样器状态对象
//创建MTLSamplerDescriptor
MTLSamplerDescriptor * desc = [[MTLSamplerDescriptor alloc] init];
desc.minFilter = MTLSamplerMinMagFilterLinear;
desc.magFilter = MTLSamplerMinMagFilterLinear;
desc.sAddressMode = MTLSamplerAddressModeRepeat;
desc.tAddressMode = MTLSamplerAddressModeRepeat;
//以下所有属性都有默认值
desc.mipFilter = MTLSamplerMipFilterNotMipmapped;
desc.maxAnisotropy = 1U;
desc.normalizedCoords = YES;
desc.lodMinClamp = 0.0f;
desc.lodMaxClamp = FLT_MAX;
//创建MTLSamplerState
id <MTLSamplerState> sampler = [device newSamplerStateWithDescriptor:desc];
保持CPU和GPU内存之间的一致性
CPU和GPU都可以访问MTLResource对象的底层存储。但是,GPU从主机CPU异步运行,因此在使用主机CPU访问这些资源的存储时,请注意以下几点。
执行MTLCommandBuffer对象时,MTLDevice只有在对象提交之前(并且只有这些更改)由主机CPU进行时,对象才能保证观察到主机CPU对MTLResource由该MTLCommandBuffer对象引用的任何对象的存储分配所做的任何更改MTLCommandBuffer。也就是说,MTLDevice对象可能不会观察到主机CPU在MTLCommandBuffer对象被提交后所做的资源的更改(即对象的status属性)。MTLCommandBufferMTLCommandBufferStatusCommitted
类似地,在MTLDevice对象执行MTLCommandBuffer对象之后,MTLDevice如果命令缓冲区已经完成执行(即,对象的status属性),则主机CPU只能保证观察对象对该命令缓冲区引用的任何资源的存储分配的任何更改MTLCommandBuffer是MTLCommandBufferStatusCompleted)。