Why does a MTLHeap texture use *so much more* memory than a device allocated texture?

Why does a MTLHeap allocated texture use so much more memory than a device allocated texture?


Taking a 1080x1920 (+mipmaps) texture allocation:


device: ~11MB

heap: ~21MB



It's even more out of control when you're allocating an 4xMSAA buffer for an iPhone X sized display. A view of 1125x2172 pixels takes:

heap: 128MB

device: 40MB


The only difference is calling device.makeTexture or heap.makeTexture.


Thanks for any help!


Example 1080P sized texture:

(lldb) po descriptor

<MTLTextureDescriptorInternal: 0x281636440>

textureType = MTLTextureType2D

pixelFormat = MTLPixelFormatRGBA8Unorm

width = 1080

height = 1920

depth = 1

mipmapLevelCount = 11

sampleCount = 1

arrayLength = 1

cpuCacheMode = MTLCPUCacheModeDefaultCache

storageMode = MTLStorageModeShared

resourceOptions = MTLResourceCPUCacheModeDefaultCache MTLResourceStorageModeShared

framebufferOnly = 0

usage = MTLTextureUsageShaderRead MTLTextureUsageShaderWrite

swizzle = [MTLTextureSwizzleRed, MTLTextureSwizzleGreen, MTLTextureSwizzleBlue, MTLTextureSwizzleAlpha]

allowGPUOptimizedContents = YES

forceResourceIndex 0

resourceIndex 0


Texture size, just after calling MTLDevice.makeTexture(descriptor:)

(lldb) p texture.allocatedSize

(Int) $R10 = 11517952


Size if allocated on a MTLHeap:

(lldb) p device.heapTextureSizeAndAlign(descriptor: descriptor)

(MTLSizeAndAlign) $R12 = {

size = 22369792

align = 16384

}





MSAA buffer:

(lldb) po descriptor

<MTLTextureDescriptorInternal: 0x280045180>

textureType = MTLTextureType2DMultisample

pixelFormat = MTLPixelFormatRGBA8Unorm

width = 1125

height = 2172

depth = 1

mipmapLevelCount = 1

sampleCount = 4

arrayLength = 1

cpuCacheMode = MTLCPUCacheModeDefaultCache

storageMode = MTLStorageModeShared

resourceOptions = MTLResourceCPUCacheModeDefaultCache MTLResourceStorageModeShared

framebufferOnly = 0

usage = MTLTextureUsageShaderRead MTLTextureUsageShaderWrite

swizzle = [MTLTextureSwizzleRed, MTLTextureSwizzleGreen, MTLTextureSwizzleBlue, MTLTextureSwizzleAlpha]

allowGPUOptimizedContents = YES

forceResourceIndex 0

resourceIndex 0



(lldb) p texture.allocatedSize

(Int) $R16 = 40108032



(lldb) p device.heapTextureSizeAndAlign(descriptor: descriptor)

(MTLSizeAndAlign) $R14 = {

size = 134217728

align = 16384

}

Accepted Reply

For anyone else interested, I filed a bug and received the following response:


This is unfortunately a hardware limitation on pre-A12 devices. Texture resolutions must be padded out to powers of two internally. Non-heap allocations can use virtual memory tricks to minimize this cost but heaps cannot.

Replies

FYI: measured on iOS 12.1.2 using an iPhone X.

Because of mipmap level count. You have 11 in the first case and 1 in the second.

I think you're comparing the 2 different allocation examples I gave (the first example is a mipmapped texture and the second example is 4xMSAA framebuffer which, of course, I expect to be different).


I received a response from Apple engineering as I filed this as a radar bug, see later reply.

For anyone else interested, I filed a bug and received the following response:


This is unfortunately a hardware limitation on pre-A12 devices. Texture resolutions must be padded out to powers of two internally. Non-heap allocations can use virtual memory tricks to minimize this cost but heaps cannot.

Indeed, sorry for my inattention.

Thank you for the bug report foobar.


Although this doesn't address the internal padding mentioned above, I think you'll find this article helpful for understanding memroy usage and reducing the memory footprint of your Metal apps in general.


https://developer.apple.com/documentation/metal/reducing_the_memory_footprint_of_metal_apps?language=objc