Многослойный OpenGLView перерисовывается только при изменении размера окна - PullRequest
9 голосов
/ 30 сентября 2011

У меня есть окно с основным видом типа NSView и подвидом, который является подклассом NSOpenGLView с именем CustomOpenGLView.Подкласс NSOpenGLView получается через Custom View в Интерфейсном Разработчике и путем установки его класса в CustomOpenGLView.Это сделано в соответствии с примером Apple Code Layer Backed OpenGLView .

Приложение создано для рисования чего-либо в OpenGLContext каждые, скажем, 0,05 секунды.С отключенным Core Animation Layer я могу видеть движущийся объект в виде, что является следствием непрерывного перерисовывания вида.И все работает безупречно.

Теперь я хочу иметь полупрозрачный вид поверх CustomOpenGLView для размещения кнопок управления, таких как play / stop / ecc ..

Для этого мне нужно добавитьподпредставление к CustomOpenGLView и я включил Core Animation Layer на CustomOpenGLView.Кнопки управления размещены в этом новом подпредставлении.

Таким образом, представление с кнопками управления правильно отображается поверх CustomOpenGLView, но теперь представление не перерисовывается.Он рисует только если я изменяю размер окна, содержащего все эти виды.

В результате я не вижу никакой "анимации" ... Я вижу только неподвижное изображение, которое представляет первый кадр, который рисуется, когдацикл рисования начинается.Если я изменяю размер окна, openGLContext перерисовывается, пока я не перестану изменять размер окна.После этого я снова вижу неподвижное изображение с последним рисунком во время изменения размера.

Кроме того, когда цикл рисования начинается, на экране появляется только первый «кадр», и если я изменяю размер окна, давайтескажем, через 5 секунд я вижу в представлении, что именно должно было быть нарисовано через 5 секунд после начала цикла рисования.Кажется, мне нужно установить [glView setNeedsDisplay:TRUE].Я сделал это, но ничего не изменилось.

Где ошибка?Почему добавление Core Animation Layer нарушает перерисовку?Означает ли это что-то, что я не получаю?

1 Ответ

23 голосов
/ 26 июня 2012

Если у вас есть нормальный NSOpenGLView, вы можете просто нарисовать что-то через OpenGL и затем вызвать -flushBuffer из NSOpenGLContext, чтобы рендеринг появился на экране. Если ваш контекст не является двойной буферизацией, что не нужно, если вы визуализируете в окно, так как все окна уже дважды буферизованы в MacOS X, достаточно также вызвать glFlush() (только для реального полноэкранного рендеринга OpenGL вы ' понадобится двойная буферизация, чтобы избежать артефактов). Затем OpenGL будет отображаться непосредственно в пиксельном хранилище вашего представления (которое фактически является резервным хранилищем окна) или, в случае двойной буферизации, он будет рендериться в обратный буфер, а затем заменять его фронт-буфером; таким образом, новый контент сразу виден на экране (фактически не раньше следующего обновления экрана, но такое обновление происходит по крайней мере 50-60 раз в секунду).

Все немного по-другому, если NSOpenGLView поддерживается на слое. Когда вы вызываете -flushBuffer или glFlush(), рендеринг действительно происходит так же, как и раньше, и снова, изображение напрямую отображается в пиксельном хранилище вида, однако это пиксельное хранилище не является резервным хранилищем окно больше, это «слой поддержки» представления. Таким образом, ваше изображение OpenGL обновляется, вы просто не видите, как это происходит, поскольку «рисование в слое» и «отображение слоя на экране» - это две совершенно разные вещи! Чтобы сделать содержимое нового слоя видимым, вам нужно вызвать setNeedsDisplay:YES на вашем слое NSOpenGLView.

Почему у вас не получилось, когда вы позвонили setNeedsDisplay:YES? Прежде всего, убедитесь, что вы выполняете этот вызов в главном потоке. Вы можете выполнить этот вызов в любом потоке, который вам нравится, он наверняка пометит представление как грязное, но только при выполнении этого вызова в основном потоке он также запланирует вызов перерисовки (без этого вызова он помечается как грязный, но не будет перерисован, пока не будет перерисован любой другой родительский / дочерний вид). Другой проблемой может быть метод drawRect:. Когда вы помечаете представление как грязное, и оно перерисовывается, этот метод вызывается, и что бы этот метод не «рисовал», он перезаписывает любой контент, находящийся в данный момент в слое. До тех пор, пока ваше представление не было зашито слоями, не имело значения, где вы рендерили содержимое OpenGL, но для представления со слоями, это на самом деле метод, в котором вы должны выполнять все свои рисунки.

Попробуйте сделать следующее: Создайте NSTimer в вашем главном потоке, который запускается каждые 20 мс и вызывает метод, который вызывает setNeedsDisplay:YES для вашего слоя NSOpenGLView. Переместите весь ваш код рендеринга OpenGL в метод drawRect: вашего слоя NSOpenGLView. Это должно работать довольно хорошо. Если вам нужно что-то более надежное, чем NSTimer, попробуйте CVDisplayLink (CV = CoreVideo). CVDisplayLink походит на таймер, но срабатывает каждый раз, когда экран перерисовывается.

Обновление

Многослойный NSOpenGLView несколько устарел, начиная с 10.6, он больше не нужен. Внутренне NSOpenGLView создает NSOpenGLLayer, когда вы делаете его многоуровневым, так что вы также можете напрямую использовать такой слой и «создавать» свой собственный NSOpenGLView:

  1. Создайте свой собственный подкласс NSOpenGLLayer, назовем его MyOpenGLLayer
  2. Создайте свой собственный подкласс NSView, назовем его MyGLView
  3. Переопределить - (CALayer *)makeBackingLayer, чтобы вернуть автоматически выпущенный экземпляр MyOpenGLLayer
  4. Набор wantsLayer:YES для MyGLView

Теперь у вас есть собственный вид с поддержкой слоев, и он поддерживается вашим подклассом NSOpenGLLayer. Так как это слой с подложкой , абсолютно нормально добавлять к нему подвиды (например, кнопки, текстовые поля и т. Д.).

Для вашего заднего слоя у вас есть два основных варианта.

Вариант 1
Правильный и официально поддерживаемый способ - сохранить рендеринг в основном потоке. Для этого вы должны сделать следующее:

  • Переопределить canDrawInContext:... для возврата YES / NO, в зависимости от того, можете ли вы / хотите рисовать следующий кадр или нет.
  • Переопределите drawInContext:..., чтобы выполнить фактический рендеринг OpenGL.
  • Сделать слой асинхронным (setAsynchronous:YES)
  • Убедитесь, что слой «обновляется» при каждом изменении его размера (setNeedsDisplayOnBoundsChange:YES), в противном случае базовая поверхность OpenGL не изменяется при изменении размера слоя (ивизуализированный контекст OpenGL должен растягиваться / сокращаться каждый раз, когда слой перерисовывается)

Apple создаст для вас CVDisplayLink, который будет вызывать canDrawInContext:... в главном потоке при каждом запуске и, если этот методвозвращает YES, вызывает drawInContext:....Вот как вы должны это делать.

Если ваш рендеринг слишком дорог, чтобы выполняться в главном потоке, вы можете сделать следующий трюк: Переопределить openGLContextForPixelFormat:..., чтобы создать общий контекст (Контекст B), который является общимс другим контекстом, который вы создали ранее (Контекст A).Создайте кадровый буфер в контексте A (вы можете сделать это до или после создания контекста B, это на самом деле не имеет значения);При необходимости добавьте буферы рендеринга глубины и / или трафарета (на небольшую глубину по вашему выбору), однако вместо цветового буфера рендеринга добавьте «текстуру» (Texture X) в качестве цветовых вложений (glFramebufferTexture()).Теперь весь вывод цветного рендера записывается в эту текстуру при рендеринге в этот кадровый буфер.Выполните все рендеринг в этот кадровый буфер, используя Context A в любом потоке на ваш выбор!Когда рендеринг завершен, сделайте canDrawInContext:... return YES и в drawInContext:... просто нарисуйте простой quad , который заполняет весь активный кадровый буфер (Apple уже установил его для вас, а также окно просмотра для заполнения).это полностью), и это текстурируется с помощью Texture X. Это возможно, поскольку совместно используемые контексты совместно используют все объекты (например, текстуры, кадровые буферы и т. д.).Таким образом, ваш drawInContext:... метод никогда не будет делать больше, чем рисование одного простого текстурированного квадрата, вот и все.Все остальные (возможно, дорогостоящие рендеринг) происходят с этой текстурой в фоновом потоке и не блокируют ваш основной поток.

Опция 2
Другая опция официально не поддерживается Apple иможет или не может работать для вас:

  • Не переопределять canDrawInContext:..., реализация по умолчанию всегда возвращает YES, и это то, что вы хотите.
  • Переопределить drawInContext:... длявыполнить фактический рендеринг OpenGL, все это.
  • Не делайте слой асинхронным.
  • Не устанавливайте needsDisplayOnBoundsChange.

Где бы вы ни захотеличтобы перерисовать этот слой, вызовите display напрямую ( НЕ setNeedsDisplay! Это правда, Apple говорит, что вы не должны вызывать его, но «не должен» не значит «не должен») и послезвоните display, звоните [CATransaction flush].Это будет работать даже при вызове из фонового потока!Ваш метод drawInContext:... вызывается из того же потока, который вызывает display, который может быть любым потоком.При непосредственном вызове display ваш код рендеринга OpenGL будет выполнен, однако вновь отображаемый контент все еще виден только в резервном хранилище слоя, чтобы вывести его на экран, вы должны заставить систему выполнить наложение слоев, а [CATransaction flush]сделать именно это.Класс CATransaction, который имеет только методы класса (вы никогда не создадите его экземпляр), является неявно поточно-ориентированным и всегда может использоваться из любого потока в любое время (он выполняет блокировку самостоятельно в любое время и в любом месте).

Хотя этот метод не рекомендуется, поскольку он может вызвать проблемы с перерисовкой для других представлений (поскольку они могут также перерисовываться в потоках, отличных от основного потока, и не все представления поддерживают это), также не запрещается , он не использует частный API и был предложен в списке рассылки Apple, и никто в Apple не против.

...