Слой обеспеченных OpenGLView перерисовывает только при изменении размера окна

у меня есть окно с основным видом типа NSView и подвида, который является подклассом NSOpenGLView, которого зовут CustomOpenGLView. Подкласс NSOpenGLView в результате Custom View в Interface Builder и установив свой класс в CustomOpenGLView. Это сделано в соответствии с образцом кода Apple Слой Поддерживает OpenGLView.

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

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

для этого у меня есть добавить subview в CustomOpenGLView и я включил основной анимационный слой на CustomOpenGLView. Кнопки управления помещаются в этот новый подпанели.

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

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

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

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

1 ответов


когда у вас есть нормальный 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 похоже на таймер, но он срабатывает каждый раз, когда экран только что был перерисован.

обновление

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

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

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

для подкладочного слоя, у вас есть в основном два варианта.

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

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

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

если ваш рендеринг слишком дорог, чтобы произойти в основном потоке, вы можете сделать следующий трюк: Override openGLContextForPixelFormat:... чтобы создать контекст (контекст B), который совместно используется с другим контекстом, созданным ранее (контекст A). Создать фрейм в контексте (вы можете сделать это до или после создания контекста, это не очень важно); придаем глубину и/или трафарет renderbuffers при необходимости (бит глубина на ваш выбор), однако вместо цвет renderbuffer, прикрепить ткань (текстура х) а цвет вложения (glFramebufferTexture()). Теперь все выходные данные цветового рендеринга записываются в эту текстуру при рендеринге в этот фреймбуфер. Выполните весь рендеринг для этого фреймбуфера, используя контекст A в любом потоке по вашему выбору! Как только рендеринг сделан, make canDrawInContext:... возвращение YES и drawInContext:... просто нарисуйте простой quad который заполняет весь активный фреймбуффер (Apple уже установила его для вас, а также окно просмотра, чтобы заполнить его полностью), и который текстурирован текстурой 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, выступающего против него.