Как настроить байтовое выравнивание из MTLBuffer в 2D MTLTexture? - PullRequest
0 голосов
/ 18 июня 2020

У меня есть массив значений с плавающей запятой, представляющий 2D-изображение (думаю, из CCD), которое я в конечном итоге хочу визуализировать в MTLView. Это на macOS, но я хотел бы иметь возможность применить то же самое к iOS в какой-то момент. Сначала я создаю MTLBuffer с данными:

NSData *floatData = ...;
id<MTLBuffer> metalBuffer = [device newBufferWithBytes:floatData.bytes
                                                length:floatData.length
                                               options:MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged];

Отсюда я запускаю буфер через несколько вычислительных конвейеров. Затем я хочу создать объект RGB MTLTexture для передачи нескольким фильтрам 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 Ответ

1 голос
/ 18 июня 2020

Если для вас важно, чтобы текстура использовала ту же резервную память, что и буфер, и вы хотите, чтобы текстура отражала фактические размеры изображения, вам необходимо убедиться, что данные в буфере правильно выровнены с самого начала.

Вместо того, чтобы копировать все исходные данные сразу, вам нужно убедиться, что в буфере есть место для всех выровненных данных, а затем копировать его по одной строке за раз.

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;
}

Вам решать, стоит ли сохранять копию дополнительной сложности, но это один из способов добиться того, чего вы хотите.

...