Как настроить байтовое выравнивание из MTLBuffer в 2D MTLTexture?
У меня есть массив значений с плавающей запятой, представляющий 2D-изображение (думаю, из ПЗС-матрицы), которое я в конечном итоге хочу отобразить в MTLView
. Это на macOS, но я хотел бы иметь возможность применить то же самое к iOS в какой-то момент. Сначала я создаюMTLBuffer
с данными:
NSData *floatData = ...;
id<MTLBuffer> metalBuffer = [device newBufferWithBytes:floatData.bytes
length:floatData.length
options:MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged];
Отсюда я пропускаю буфер через несколько вычислительных конвейеров. Затем я хочу создать RGBMTLTexture
объект передать нескольким CIFilter
/MPS фильтрует, а затем отображает. Кажется, имеет смысл создать текстуру, которая использует уже созданный буфер в качестве основы, чтобы избежать создания новой копии. (Я успешно использовал текстуры с форматом пикселейMTLPixelFormatR32Float
.)
// create texture with scaled buffer - this is a wrapper, i.e. it shares memory with the buffer
MTLTextureDescriptor *desc;
desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Float
width:width
height:height
mipmapped:NO];
desc.usage = MTLResourceUsageRead;
desc.storageMode = scaledBuffer.storageMode; // must match buffer
id<MTLTexture> scaledTexture = [scaledBuffer newTextureWithDescriptor:desc
offset:0
bytesPerRow:imageWidth * sizeof(float)];
Размеры изображения - 242x242. Когда я запускаю это, я получаю:
validateNewTexture:89: failed assertion `BytesPerRow of a buffer-backed
texture with pixelFormat(MTLPixelFormatR32Float) must be aligned to 256 bytes,
found bytesPerRow(968)'
Я знаю, что мне нужно использовать:
NSUInteger alignmentBytes = [self.device minimumLinearTextureAlignmentForPixelFormat:MTLPixelFormatR32Float];
Как определить буфер, чтобы байты были правильно выровнены?
В целом, подходит ли это для такого рода данных? Это этап, на котором я эффективно конвертирую плавающие данные во что-то, имеющее цвет. Чтобы уточнить, это мой следующий шаг:
// render into RGB texture
MPSImageConversion *imageConversion = [[MPSImageConversion alloc] initWithDevice:self.device
srcAlpha:MPSAlphaTypeAlphaIsOne
destAlpha:MPSAlphaTypeAlphaIsOne
backgroundColor:nil
conversionInfo:NULL];
[imageConversion encodeToCommandBuffer:commandBuffer
sourceImage:scaledTexture
destinationImage:intermediateRGBTexture];
где intermediateRGBTexture
это 2D-текстура, определяемая с помощью MTLPixelFormatRGBA16Float
чтобы воспользоваться EDR.
1 ответ
Если для вас важно, чтобы текстура использовала ту же резервную память, что и буфер, и вы хотите, чтобы текстура отражала фактические размеры изображения, вам необходимо убедиться, что данные в буфере правильно выровнены с самого начала.
Вместо того, чтобы копировать исходные данные все сразу, вам нужно убедиться, что в буфере есть место для всех выровненных данных, а затем копировать его по одной строке за раз.
NSUInteger rowAlignment = [self.device minimumLinearTextureAlignmentForPixelFormat:MTLPixelFormatR32Float];
NSUInteger sourceBytesPerRow = imageWidth * sizeof(float);
NSUInteger bytesPerRow = AlignUp(sourceBytesPerRow, rowAlignment);
id<MTLBuffer> metalBuffer = [self.device newBufferWithLength:bytesPerRow * imageHeight
options:MTLResourceCPUCacheModeDefaultCache];
const uint8_t *sourceData = floatData.bytes;
uint8_t *bufferData = metalBuffer.contents;
for (int i = 0; i < imageHeight; ++i) {
memcpy(bufferData + (i * bytesPerRow), sourceData + (i * sourceBytesPerRow), sourceBytesPerRow);
}
где AlignUp
ваша функция выравнивания или макрос по выбору. Что-то вроде этого:
static inline NSUInteger AlignUp(NSUInteger n, NSInteger alignment) {
return ((n + alignment - 1) / alignment) * alignment;
}
Вам решать, стоит ли сохранять копию из-за дополнительной сложности, но это один из способов добиться того, чего вы хотите.