資源對(duì)象:緩沖和紋理
這一章節(jié)將討論 Metal 中存儲(chǔ)未格式化數(shù)據(jù)和格式化圖片數(shù)據(jù)的資源對(duì)象 MTLResource,它主要分為兩類
- MTLBuffer 代表一個(gè)未格式化數(shù)據(jù)的存儲(chǔ)空間者疤,能夠保存保存任意類型數(shù)據(jù)福澡,緩沖經(jīng)常用于頂點(diǎn)、著色器以及計(jì)算狀態(tài)數(shù)據(jù)
- MTLTexture 代表一個(gè)有確定類型和像素格式的格式化圖片數(shù)據(jù)的存儲(chǔ)空間宛渐,常作為頂點(diǎn)竞漾、片段和計(jì)算函數(shù)的源紋理,或者存儲(chǔ)圖形渲染的輸出(即作為附件)
本章也會(huì)討論 MTLSamplerState窥翩。盡管采樣器本身并不是資源业岁,但它們常被用于對(duì)一個(gè)紋理對(duì)象執(zhí)行查找計(jì)算。
緩沖是內(nèi)存的無類型分配
一個(gè) MTLBuffer 對(duì)象代表著一個(gè)能裝載任意類型數(shù)據(jù)的內(nèi)存分配空間寇蚊。
創(chuàng)建一個(gè)緩沖對(duì)象
下面這些 MTLDevice 方法可以創(chuàng)建并返回一個(gè) MTLBuffer 對(duì)象
-
newBufferWithLength:options:
使用新的存儲(chǔ)分配空間創(chuàng)建一個(gè) MTLBuffer 對(duì)象 -
newBufferWithBytes:length:options:
從一片已知的內(nèi)存空間(通過 CPU 地址指針尋址)中拷貝數(shù)據(jù)到新的存儲(chǔ)分配空間并創(chuàng)建一個(gè) MTLBuffer 對(duì)象 -
newBufferWithBytesNoCopy:length:options:deallocator:
不創(chuàng)建新的存儲(chǔ)空間笔时,直接將現(xiàn)有的存儲(chǔ)空間創(chuàng)建為一個(gè) MTLBuffer 對(duì)象
所有緩沖創(chuàng)建方法都需要一個(gè)表示存儲(chǔ)空間大小的 length 參數(shù),它的單位是字節(jié),同時(shí)所有創(chuàng)建方法還接受一個(gè) MTLResourceOptions 對(duì)象作為 options,從而可以修改創(chuàng)建出來的緩沖的特性奕坟。如果 options 傳 0,則采用默認(rèn)值较锡。
緩沖方法
MTLBuffer 協(xié)議有以下方法
-
contents
方法返回緩沖的存儲(chǔ)空間的 CPU 地址 -
newTextureWithDescriptor:offset:bytesPerRow:
方法返回一個(gè)特定類型的紋理對(duì)象,這個(gè)紋理對(duì)象指向緩沖內(nèi)的數(shù)據(jù)盗痒。更多信息請(qǐng)查閱 創(chuàng)建一個(gè)紋理對(duì)象
紋理是格式化的圖像數(shù)據(jù)
一個(gè) MTLTexture 對(duì)象代表了一個(gè)格式化后的圖像數(shù)據(jù)的內(nèi)存空間蚂蕴,它可以被用于頂點(diǎn)著色器、片段著色器和計(jì)算函數(shù)的資源,或者作為一個(gè)渲染目標(biāo)附件骡楼。MTLTexture 支持以下幾種數(shù)據(jù)結(jié)構(gòu):
- 1D熔号,2D,3D 圖像
- 1D鸟整,2D引镊,3D 圖像數(shù)組
- 六個(gè) 2D 圖像的立方體
MTLPixelFormat 確定了紋理對(duì)象中單個(gè)像素的結(jié)構(gòu),詳細(xì)的像素格式請(qǐng)查閱 紋理中的像素格式
創(chuàng)建一個(gè)紋理對(duì)象
下面這些方法可以創(chuàng)建并返回一個(gè)紋理對(duì)象:
- MTLDevice 類的
newTextureWithDescriptor:
方法為紋理圖像數(shù)據(jù)開辟一個(gè)新的內(nèi)存空間并創(chuàng)建一個(gè) MTLTexture 對(duì)象篮条,它將根據(jù)傳入的 MTLTextureDescriptor 對(duì)象設(shè)置此紋理的屬性 - MTLTexture 類的
newTextureViewWithPixelFormat:
方法創(chuàng)建一個(gè)與調(diào)用者共享內(nèi)存空間的 MTLTexture 對(duì)象弟头,由于它們共享相同的內(nèi)存空間,所有在新紋理對(duì)象上的改動(dòng)都會(huì)反映到調(diào)用對(duì)象上兑燥,反之亦然亮瓷。對(duì)于新創(chuàng)建的紋理琴拧,newTextureViewWithPixelFormat:
將會(huì)重新按照傳入的像素格式詮釋調(diào)用對(duì)象內(nèi)存儲(chǔ)的圖像數(shù)據(jù)降瞳。傳入的 MTLPixelFormat 必須與原調(diào)用對(duì)象的 MTLPixelFormat 兼容(查閱 紋理像素格式 了解 ordinary, packed 和 compressed 格式) - MTLBuffer 類的
newTextureWithDescriptor:offset:bytesPerRow:
方法創(chuàng)建一個(gè)與調(diào)用者共享內(nèi)存空間的 MTLTexture 對(duì)象蚓胸,由于它們共享相同的內(nèi)存空間挣饥,所有在新紋理對(duì)象上的改動(dòng)都會(huì)反映到調(diào)用對(duì)象上,反之亦然沛膳。在紋理和緩沖之間共享存儲(chǔ)空間會(huì)防止使用某些紋理優(yōu)化扔枫,如像素調(diào)整或平鋪。
利用紋理描述符創(chuàng)建一個(gè)紋理對(duì)象
MTLTextureDescriptor 類定義了創(chuàng)建一個(gè)紋理對(duì)象所需要的屬性锹安,包括圖片尺寸(寬短荐、高、深度)叹哭、像素格式忍宋、布局格式(數(shù)組或立方體)以及 mipmap 的個(gè)數(shù)。MTLTextureDescriptor 僅用于創(chuàng)建 MTLTexture 對(duì)象的過程中风罩,一旦創(chuàng)建完成糠排,對(duì) MTLTextureDescriptor 的屬性修改將不再對(duì) MTLTexture 生效。
利用描述符創(chuàng)建一個(gè)或多個(gè)紋理對(duì)象的方法如下:
1 創(chuàng)建一個(gè) MTLTextureDescriptor 對(duì)象超升,包含描述紋理數(shù)據(jù)的紋理屬性:
- textureType 確定紋理的維度和布局
- width入宦、height、depth 確定基級(jí)紋理 mipmap 的每一個(gè)維度的像素尺寸
- pixelFormat 確定紋理如何存儲(chǔ)一個(gè)像素
- arrayLength 確定 MTLTextureType1DArray 或 MTLTextureType2DArray 類型的紋理對(duì)象中室琢,數(shù)組元素的個(gè)數(shù)
- mipmapLevelCount 確定 mipmap 層級(jí)的個(gè)數(shù)
- sampleCount 確定每一個(gè)像素的樣本個(gè)數(shù)
2 調(diào)用 MTLDevice 類的 newTextureWithDescriptor:
方法乾闰,利用 MTLTextureDescriptor 描述符創(chuàng)建一個(gè)紋理對(duì)象。創(chuàng)建完紋理后盈滴,調(diào)用 replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:
方法加載紋理圖片數(shù)據(jù)涯肩,詳細(xì)查閱 復(fù)制圖片數(shù)據(jù)到紋理以及從紋理復(fù)制出圖片數(shù)據(jù)
3 可以復(fù)用 MTLTextureDescriptor 對(duì)象,修改其中屬性以創(chuàng)建出更多的紋理
listing 3-1 代碼展示了如何創(chuàng)建一個(gè)紋理描述符 txDesc,并設(shè)置它的屬性為 3D, 64x64x64 紋理
//Listing 3-1 Creating a Texture Object with a Custom Texture Descriptor
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];
使用紋理切片
一個(gè)紋理切片就是一個(gè)單獨(dú)的 1D宽菜、2D谣膳、3D 紋理圖片及所有與之相關(guān)的 mipmap。對(duì)于每一個(gè)切片:
- 基級(jí) mipmap 的尺寸由紋理描述符 MTLTextureDescriptor 對(duì)象的 width铅乡、height继谚、depth 屬性確定
- i 級(jí) mipmap 的縮放尺寸由
max(1, floor(width / 2i)) x max(1, floor(height / 2i)) x max(1, floor(depth / 2i))
確定,頂級(jí) mipmap 的尺寸為 1X1X1 - 一個(gè)切片的 mipmap 層級(jí)可以由
floor(log2(max(width, height, depth)))+1
確定
所有紋理都有至少一個(gè)切片阵幸,立方體和數(shù)組類型的紋理可能有多個(gè)切片花履。在 復(fù)制圖片數(shù)據(jù)到紋理以及從紋理復(fù)制出圖片數(shù)據(jù) 中討論的讀寫紋理圖片數(shù)據(jù)的方法里,slice 是一個(gè)從 0 開始的值挚赊。對(duì)于 1D诡壁、2D、3D 紋理荠割,由于只有一個(gè)切片妹卿,所以 slice 必須為 0。對(duì)于立方體紋理由于有 6 個(gè) 2D 紋理蔑鹦,所以 slice 從 0 到 5夺克。對(duì)于 1DArray 和 2DArray 紋理類型,每一個(gè)數(shù)組元素代表一個(gè)切片嚎朽。從整體紋理結(jié)構(gòu)中選擇單個(gè) 1D铺纽,2D 或 3D 圖像時(shí),首先選擇一個(gè)切片哟忍,其次選擇一個(gè)切片內(nèi)的 mipmap 層級(jí)狡门。
便捷方式創(chuàng)建紋理描述符
對(duì)于一般的 2D 和立方體紋理,使用下面的便捷方式會(huì)創(chuàng)建一個(gè) MTLTextureDescriptor 對(duì)象锅很,并自動(dòng)填充一些屬性:
-
texture2DDescriptorWithPixelFormat:width:height:mipmapped:
方法為 2D 紋理創(chuàng)建一個(gè) MTLTextureDescriptor 對(duì)象其馏,其中 width 和 height 參數(shù)定義 2D 紋理的尺寸,type 參數(shù)自動(dòng)設(shè)置為 MTLTextureType2D粗蔚,depth 和 arrayLength 被設(shè)置為 1 -
textureCubeDescriptorWithPixelFormat:size:mipmapped:
方法為一個(gè)立方體紋理創(chuàng)建 MTLTextureDescriptor 對(duì)象尝偎,type 設(shè)置為 MTLTextureTypeCube,width 和 height 被設(shè)置為 size 參數(shù)的值鹏控,depth 和 arrayLength 被設(shè)置為 1
兩個(gè)便捷方法都接受一個(gè) pixFormat 輸入?yún)?shù)用于定義紋理的像素格式致扯,一個(gè) mipmapped 參數(shù)用于定義紋理是否使用紋理切片。
listing 3-2 使用 texture2DDescriptorWithPixelFormat:width:height:mipmapped:
方法創(chuàng)建了一個(gè) 64*64 未使用紋理切片的 2D 紋理描述符對(duì)象
MTLTextureDescriptor *texDesc = [MTLTextureDescriptor
texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
width:64 height:64 mipmapped:NO];
id <MTLTexture> myTexture = [device newTextureWithDescriptor:texDesc];
復(fù)制圖片數(shù)據(jù)到紋理以及從紋理復(fù)制出圖片數(shù)據(jù)
下面的方法支持復(fù)制圖片數(shù)據(jù)到紋理以及從紋理復(fù)制出圖片數(shù)據(jù):
-
replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:
從調(diào)用者指針指向的紋理存儲(chǔ)空間中拷貝一部分區(qū)域的數(shù)據(jù)到一個(gè)確定的紋理切片中当辐,replaceRegion:mipmapLevel:withBytes:bytesPerRow:
是一個(gè)類似的便捷方法抖僵,它將拷貝數(shù)據(jù)到默認(rèn)切片中,并設(shè)置其他相關(guān)參數(shù)為默認(rèn)參數(shù)(slice = 0缘揪,bytesPerImage = 0) -
getBytes:bytesPerRow:bytesPerImage:fromRegion:mipmapLevel:slice:
從一個(gè)確定紋理切片中獲取一段數(shù)據(jù)耍群。getBytes:bytesPerRow:fromRegion:mipmapLevel:
是一個(gè)類似的便捷方法义桂,它將從默認(rèn)切片中獲取一段數(shù)據(jù),并設(shè)置其他相關(guān)參數(shù)為默認(rèn)參數(shù)(slice = 0蹈垢,bytesPerImage = 0)
listing 3-3 展示了如何調(diào)用 replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:
方法來通過系統(tǒng)內(nèi)存中一段源數(shù)據(jù)創(chuàng)建一個(gè)紋理圖片
// 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 確定了一個(gè) MTLTexture 對(duì)象中單個(gè)像素?cái)?shù)據(jù)存儲(chǔ)空間中的顏色慷吊、深度、模板數(shù)據(jù)的組織方式曹抬,大致有三類像素格式:ordinary溉瓶,packed,compressed谤民。
- ordinary 格式僅包含正常的 8堰酿,16,32 位顏色分量张足,每個(gè)分量都安排在遞增的內(nèi)存地址中触创,第一個(gè)列出的組件位于最低地址。例如为牍, MTLPixelFormatRGBA8Unorm 代表每個(gè)顏色分量有 8 比特位哼绑,最低地址位是紅色分量,接下來是綠色吵聪,以此類推凌那。
- packed 格式混合多個(gè)分量到一個(gè) 16 或 32位值中,分量從低位向高位存儲(chǔ)吟逝。例如,MTLPixelFormatRGB10A2Uint 是一個(gè) 32 位 packed 格式赦肋,其中包含 3 個(gè) 10 比特位的顏色通道块攒,2 bit 位的 alpha 通道。
- compressed 格式以像素塊排列佃乘,每個(gè)像素塊的布局特定于一種像素格式囱井。compressed 格式僅能用于 2D,2D 數(shù)組或立方體紋理類型趣避,不能用于 1D庞呕,3D 或 2D 多重采樣類型。
MTLPixelFormatGBGR422 和 MTLPixelFormatBGRG422 格式用于存儲(chǔ) YUV 顏色空間格式的像素程帕,這些格式僅支持 2D 紋理住练,不支持 2D 數(shù)組或立方體紋理,沒有切片愁拭,寬度為偶數(shù)讲逛。
許多像素格式存儲(chǔ)的是 sRGB 規(guī)格的顏色分量(例如 MTLPixelFormatRGBA8Unorm_sRGB 或 MTLPixelFormatETC2_RGB8_sRGB)。當(dāng)一個(gè)采樣操作引用到了一個(gè) sRGB 格式的紋理時(shí)岭埠,Metal 會(huì)將 sRGB 色值轉(zhuǎn)換為線性顏色空間色值盏混,再執(zhí)行采樣操作蔚鸥。從一個(gè) sRGB 格式的分量 S 轉(zhuǎn)換為線性分量 L 的公式如下
- If S <= 0.04045, L = S/12.92
- If S > 0.04045, L = ((S+0.055)/1.055)2.4
相反,當(dāng)需要渲染到一個(gè)使用 sRGB 格式紋理的色彩渲染附件時(shí)许赃,需要將線性顏色色值轉(zhuǎn)換為 sRGB 色值
- If L <= 0.0031308, S = L * 12.92
- If L > 0.0031308, S = (1.055 * L0.41667) - 0.055
有關(guān)渲染過程中的像素格式請(qǐng)查閱 [創(chuàng)建一個(gè)渲染過程描述符](Creating a Render Pass Descriptor)
為紋理查詢創(chuàng)建采樣狀態(tài)對(duì)象
MTLSamplerState 對(duì)象定義了當(dāng)一個(gè)圖形或計(jì)算函數(shù)對(duì)一個(gè) MTLTexture 紋理對(duì)象執(zhí)行紋理采樣操作時(shí)用到的尋址止喷、濾波和其他屬性。采樣描述符定義了一個(gè)采樣狀態(tài)對(duì)象的屬性混聊,創(chuàng)建一個(gè)采樣狀態(tài)對(duì)象的過程如下:
- 調(diào)用 MTLDevice 的
newSamplerStateWithDescriptor:
方法創(chuàng)建一個(gè) MTLSamplerDescriptor 對(duì)象 - 在采樣描述符中定義所需屬性启盛,包括濾波選項(xiàng)、尋址模式技羔、最大各向異性僵闯、細(xì)節(jié)層次參數(shù)
- 通過調(diào)用 MTLDevice 的
newSamplerStateWithDescriptor:
方法從一個(gè)采樣描述符創(chuàng)建一個(gè) MTLSamplerState 對(duì)象
你可以重復(fù)使用采樣描述符來創(chuàng)建多個(gè)采樣狀態(tài)對(duì)象,還可以修改所需要的描述符的屬性藤滥。描述符的屬性值僅在創(chuàng)建采樣狀態(tài)對(duì)象過程中生效鳖粟。一旦采樣狀態(tài)對(duì)象生成了,再修改描述符的屬性將不會(huì)對(duì)采樣狀態(tài)對(duì)象產(chǎn)生任何影響拙绊。
Listing 3-4 是一個(gè)示例代碼向图,通過創(chuàng)建并配置一個(gè) MTLSamplerDescriptor 對(duì)象來創(chuàng)建一個(gè) MTLSamplerState 對(duì)象,其中定義了這個(gè)描述符的濾波和尋址模式屬性标沪。然后調(diào)用了 newSamplerStateWithDescriptor:
榄攀,利用采樣描述符創(chuàng)建了一個(gè)采樣狀態(tài)對(duì)象。
// create MTLSamplerDescriptor
MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
desc.minFilter = MTLSamplerMinMagFilterLinear;
desc.magFilter = MTLSamplerMinMagFilterLinear;
desc.sAddressMode = MTLSamplerAddressModeRepeat;
desc.tAddressMode = MTLSamplerAddressModeRepeat;
// all properties below have default values
desc.mipFilter = MTLSamplerMipFilterNotMipmapped;
desc.maxAnisotropy = 1U;
desc.normalizedCoords = YES;
desc.lodMinClamp = 0.0f;
desc.lodMaxClamp = FLT_MAX;
// create MTLSamplerState
id <MTLSamplerState> sampler = [device newSamplerStateWithDescriptor:desc];
維護(hù) CPU 與 GPU 內(nèi)存間相干性
CPU 和 GPU 均能訪問一個(gè) MTLResource 對(duì)象的底層存儲(chǔ)金句,但是 GPU 和主 CPU 的操作是異步的檩赢,所以在使用主 CPU 方法訪問這些資源的存儲(chǔ)時(shí)要牢記下面的要點(diǎn)。
當(dāng)執(zhí)行一個(gè) MTLCommandBuffer 對(duì)象時(shí)违寞,MTLDevice 對(duì)象僅能保證在 MTLCommandBuffer 對(duì)象被提交之前觀察到主機(jī) CPU 對(duì)這個(gè) MTLCommandBuffer 引用到的 MTLResource 對(duì)象所指向的內(nèi)存空間的任何改動(dòng)贞瞒。一旦相應(yīng)的 MTLCommandBuffer 被提交,則 MTLDevice 就無法再觀察到主 CPU 對(duì)資源所做的修改(即趁曼,MTLCommandBuffer 對(duì)象的 status 為 MTLCommandBufferStatusCommitted)军浆。
類似的,在 MTLDevice 執(zhí)行完一個(gè) MTLCommandBuffer 對(duì)象后挡闰,主 CPU 僅在命令緩沖完全被完成時(shí)乒融,才能保證觀察到 MTLDevice 通過命令緩沖對(duì)任何引用到的資源內(nèi)存空間的修改(即,MTLCommandBuffer 對(duì)象的 status 為 MTLCommandBufferStatusCompleted)摄悯。