Быстрая альтернатива drawInRect

#objective-c #macos #cocoa

#objective-c #macos #какао

Вопрос:

Какой самый быстрый способ рендеринга и масштабирования изображения на поверхности с высокой частотой кадров?

Я рисую (с масштабированием) NSBitmapImageRep, используя drawRect со скоростью ~ 30 кадров в секунду, но он использует огромное количество процессора.

Пример кода для установки пикселей в NSBitmapImageRep со скоростью 30 кадров в секунду:

 NSInteger x, y;
unsigned char *imgptr = nsBitmapImageRepObj.bitmapData;
unsigned char *ptr;
NSInteger rowBytes = nsBitmapImageRepObj.bytesPerRow;

for (y = 0; y < nsInputFrameRect.size.height; y  ) {
    ptr = imgptr   (y * rowBytes);
    for (x = 0; x < nsInputFrameRect.size.width; x  ) {
        *ptr   = 1; // R
        *ptr   = 2; // G
        *ptr   = 3; // B
    }

}
[self setNeedsDisplay:YES];
  

Отрисовка происходит со скоростью 30 кадров в секунду:

 - (void)drawRect:(NSRect)pRect {
    [NSGraphicsContext saveGraphicsState];    
    [nsBitmapImageRepObj drawInRect:pRect];
    [NSGraphicsContext restoreGraphicsState];    
} // end drawRect
  

drawInRect в NSBitmapImageRep использует много процессора. Какой самый быстрый способ масштабирования и рисования изображения с высокой частотой кадров? Исходным изображением должно быть изображение, для которого я могу установить пиксели напрямую, например, путем получения указателя BitmapData.

Если я преобразовываю NSBitmapImageRep в CIImage и рисую с помощью [CIImage drawInRect], то это примерно на 10% быстрее. Если я буду рисовать в NSOpenGLView вместо NSView, это снова будет примерно на 10% быстрее, но все равно scale / drawInRect занимает много процессорного времени.

Комментарии:

1. Вы пытаетесь раскрасить каждый пиксель на основе формулы? Этот вид операций лучше всего подходит для графического процессора. В зависимости от того, что именно вы собираетесь делать, ваше решение может варьироваться от шейдеров OpenGL до CoreImage и даже просто Quartz.

2. Это для простой графики караоке, но это не шаблонно: я обновляю изображение NSBitmapImageRep с низким разрешением, а затем масштабирую это маленькое изображение до размера экрана со скоростью около 30 кадров в секунду. Установка пикселей в маленьком NSBitmapImageRep выполняется очень быстро, но регулярное масштабирование изображения до размера экрана с помощью drawInRect выполняется очень медленно. Быстрее ли масштабировать изображение? Или есть какой-то более быстрый способ масштабирования изображений, когда исходные изображения могут быть установлены попиксельно?

Ответ №1:

Прежде всего, вы должны удалить сохранение и восстановление NSGraphicsContext , потому -[NSImageRep drawInRect:] что метод делает это сам . См. «Контекст рисования графики Cocoa»:

 Important: Saving and restoring the current graphics state is a relatively expensive operation that should done as little as possible.
  

Следующий момент — это структура ваших пиксельных данных: 8 бит на выборку и 3 выборки на пиксель, что означает 24 бита на пиксель. Это очень неудобно для оборудования (GPU?), Потому что 32-битные значения обрабатываются быстрее, чем 24-битные значения. Есть простой способ улучшить (ускорить) обработку пикселей: используйте 4 байта вместо 3: добавьте байт заполнения к каждому пикселю, что не повредит изображению! Этот байт заполнения не является частью изображения, а только частью хранилища, в котором хранится пиксель, и не используется ни в одной операции рисования. Это просто здесь, чтобы ускорить операции с пикселями. Поэтому используйте это в описании вашего NSBitImageRep :

 bitsPerSample:8
samplesPerPixel:3
hasAlpha:NO
isPlanar:NO    // must be meshed
bytesPerRow:0  // let the system do it
bitsPerPixel:32   // <-- important !!
  

И поэтому ваш код должен быть изменен:

     *ptr   = 3; // B
    *ptr   = 123; // new!! value is unimportant
  

Кстати: если вы читаете jpeg-изображение в NSBitmapImageRep , то эти изображения всегда содержат 3-байтовые пиксели плюс байт заполнения. Зачем такая трата памяти? Для этого есть веские причины!

Комментарии:

1. Спасибо, я удалил сохранение контекста и перешел на 32 бит / с. Это имеет большой смысл, но 32 бит / с не оказали заметного влияния на производительность в моем случае. Для увеличения с 200×200 до 400×400 требуется около 7% ЦП, но для увеличения примерно до 1600×1600 требуется 34% ЦП, что, по-видимому, все в вызове drawInRect(). Возможно, NSBitmapImageRep drawInRect не использует графический процессор для меня. Является ли более целесообразным использовать CIImage, чтобы убедиться, что он обрабатывается графическим процессором?