Обработка данных канала питания на GPU (металл) и CPU (OpenCV) на iPhone

Я делаю обработку видео в реальном времени на iOS со скоростью 120 кадров в секунду и хочу сначала предварительно обработать изображение на графическом процессоре (сокращение, преобразование цвета и т.д., которые недостаточно быстры для процессора) и более поздний постпроцессный фрейм на CPU с использованием OpenCV.

Какой самый быстрый способ обмена каналами камеры между GPU и CPU с помощью Metal?

Другими словами, труба будет выглядеть так:

CMSampleBufferRef -> MTLTexture or MTLBuffer -> OpenCV Mat

Я конвертирую CMSampleBufferRef → MTLTexture следующим образом

CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

// textureRGBA
{
    size_t width = CVPixelBufferGetWidth(pixelBuffer);
    size_t height = CVPixelBufferGetHeight(pixelBuffer);
    MTLPixelFormat pixelFormat = MTLPixelFormatBGRA8Unorm;

    CVMetalTextureRef texture = NULL;
    CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, _textureCache, pixelBuffer, NULL, pixelFormat, width, height, 0, &texture);
    if(status == kCVReturnSuccess) {
        textureBGRA = CVMetalTextureGetTexture(texture);
        CFRelease(texture);
    }
}

После того, как мой металлический шейдер завершен, я конвертирую MTLTexture в OpenCV

cv::Mat image;
...
CGSize imageSize = CGSizeMake(drawable.texture.width, drawable.texture.height);
int imageByteCount = int(imageSize.width * imageSize.height * 4);
int mbytesPerRow = 4 * int(imageSize.width);

MTLRegion region = MTLRegionMake2D(0, 0, int(imageSize.width), int(imageSize.height));
CGSize resSize = CGSizeMake(drawable.texture.width, drawable.texture.height);
[drawable.texture getBytes:image.data bytesPerRow:mbytesPerRow  fromRegion:region mipmapLevel:0];

Некоторые наблюдения:

1) К сожалению, MTLTexture.getBytes кажется дорогим (копирование данных с GPU на CPU?) и занимает около 5 мс на моем iphone 5S, что слишком много при обработке со скоростью ~ 100 кадров в секунду

2) Я заметил, что некоторые люди используют MTLBuffer вместо MTLTexture со следующим методом:   metalDevice.newBufferWithLength(byteCount, options: .StorageModeShared) (см. Производительность записи памяти - Общая память процессора GPU)

Однако CMSampleBufferRef и сопутствующий CVPixelBufferRef управляется CoreVideo - это предположение.

Ответ 1

Самый быстрый способ сделать это - использовать MTLTexture, поддерживаемый MTLBuffer; это особый вид MTLTexture, который разделяет память с MTLBuffer. Однако ваша обработка C (openCV) будет работать с кадром или двумя сзади, это неизбежно, поскольку вам необходимо отправить команды на GPU (кодирование), и графический процессор должен отобразить его, если вы используете waitUntilCompleted, чтобы убедиться, что графический процессор закончен, что просто переваривает процессор и расточительно.

Таким образом, процесс будет таким: сначала вы создаете MTLBuffer, затем вы используете метод MTLBuffer "newTextureWithDescriptor: offset: bytesPerRow:" для создания специальной MTLTexture. Вам нужно заранее создать специальную MTLTexture (как переменную экземпляра), тогда вам нужно настроить стандартный конвейер рендеринга (быстрее, чем использовать вычислительные шейдеры), который примет MTLTexture, созданную из CMSampleBufferRef, и передаст это в вашу специальную MTLTexture, в которые проходят, вы можете масштабировать и выполнять любое преобразование цвета по мере необходимости за один проход. Затем вы отправляете командный буфер в gpu, в следующем проходе вы можете просто вызвать [theMTLbuffer contents], чтобы захватить указатель на байты, которые возвращают ваш специальный MTLTexture для использования в openCV.

Любая техника, которая останавливает работу CPU/GPU, никогда не будет эффективной, поскольку половина времени будет потрачена на ожидание, то есть процессор ждет завершения графического процессора, и графическому процессору придется ждать также следующих кодировок (когда GPU работает, вы хотите, чтобы процессор кодировал следующий кадр и выполнял любую работу openCV, а не ожидал завершения графического процессора).

Кроме того, когда люди обычно ссылаются на обработку в реальном времени, они обычно ссылаются на некоторую обработку с обратной связью в реальном времени (визуальную), все современные устройства iOS с 4 и более имеют частоту обновления экрана 60 Гц, поэтому любая обратная связь представлена быстрее, чем это бессмысленно, но если вам нужно 2 кадра (на 120 Гц), чтобы сделать 1 (при 60 Гц), тогда вы должны иметь собственный таймер или изменить CADisplayLink.