Я создаю графическое приложение для пользовательского интерфейса с использованием OpenGL, в котором может быть любое количество окон - стиль интерфейса нескольких документов.
Если бы было одно окно, основной цикл мог бы выглядеть так:
- обрабатывать события
- ничья()
- буферы обмена (vsync заставляет это блокироваться до обновления вертикального монитора)
Однако рассмотрим основной цикл, когда есть 3 окна:
- события каждого окна обработки
- каждое окно draw()
- окно 1 swap-буферы (блокировать до vsync)
- (через некоторое время) окно 2 свопинга (блокировать до vsync)
- (через некоторое время) окно 3 своп-буфера (блокировать до vsync)
Упс... теперь рендеринг одного кадра приложения происходит на 1/3 от правильной частоты кадров.
Обходное решение: служебное окно
Одним из способов решения является включение только одного из окон с включенным vsync, а остальные из них с помощью vsync отключены. Сначала вызовите swapBuffers() в окне vsync и нарисуйте его, затем нарисуйте остальные окна и swapBuffers() на каждом из них.
Это обходное решение, вероятно, будет отлично смотреться в большинстве случаев, но это не без проблем:
- Неэлегантно, чтобы одно окно было специальным
- состояние гонки может все еще вызвать разрывы экрана.
- некоторые платформы игнорируют настройку vsync и заставляют ее находиться на
- Я прочитал, что переключение, связанное с контекстом OpenGL, является дорогостоящей операцией, и ее следует избегать.
Обходной путь: один поток в окне
Так как для каждого потока может быть один контекст OpenGL, возможно, ответ должен состоять из одного потока на окно.
Я все же хочу, чтобы GUI был однопоточным, поэтому основной цикл для 3-оконной ситуации выглядел бы так:
(для каждого окна)
- блокировать глобальные мьютексы
- обрабатывать события
- ничья()
- разблокировать глобальные мьютексы
- SwapBuffers()
Будет ли это работать? Этот другой вопрос указывает, что он не будет:
Оказывается, что окна "сражаются" друг с другом: похоже вызовы SwapBuffers синхронизируются и ждут друг друга, даже хотя они находятся в отдельных потоках. Я измеряю рамку-в-рамку время каждого окна и с двумя окнами, это падает до 30 кадров в секунду, с от трех до 20 кадров в секунду и т.д.
Чтобы исследовать эту претензию, я создал простую тестовую программу . Эта программа создает N окон и N потоков, привязывает одно окно к потоку, запрашивает каждое окно, на которое есть vsync, а затем сообщает о частоте кадров. Пока результаты следующие:
- Linux, X11, 4.4.0 NVIDIA 346.47 (2015-04-13)
- Частота кадров составляет 60 кадров в секунду независимо от того, сколько окон открыто.
- OSX 10.9.5 (2015-04-13)
- частота кадров не ограничена; swap-буферы не блокируются.
Обходной путь: только один контекст, один большой фреймбуффер
Другая идея, о которой я думал: имеет только один контекст OpenGL и один большой фреймбуфер, размер всех окон, собранных вместе.
Каждый кадр, каждое окно вызывает glViewport
, чтобы установить соответствующий прямоугольник фреймбуфера перед рисованием.
После завершения рисования swapBuffers() в единственном контексте OpenGL.
Я собираюсь выяснить, будет ли это обходное решение работать или нет. Некоторые вопросы у меня есть:
- Будет ли у вас такой большой фреймбуфер?
- Можно ли называть
glViewport
несколько раз в каждом кадре? - Будет ли API оконной библиотеки, который я использую, даже позволяет мне создавать контексты OpenGL независимо от окон?
- Пустое пространство в фреймбуфере, если все окна разных размеров?
Камилла Берглунд, сторонник GLFW говорит:
Это не то, как работает glViewport. Не это как работает обмен буферами. Каждое окно будет иметь кадровый буфер. Вы не можете заставить их делиться ими. Обмен буферов для каждого фреймбуфера окна и контекст может быть привязан только к одному окно за раз. Это на уровне ОС, а не ограничение GLFW.
Обходной путь: только один контекст
Этот вопрос указывает, что этот алгоритм может работать:
Activate OpenGL context on window 1
Draw scene in to window 1
Activate OpenGL context on window 2
Draw scene in to window 2
Activate OpenGL context on window 3
Draw scene in to window 3
For all Windows
SwapBuffers
По словам вопросника,
При включенной поддержке V-Sync SwapBuffers синхронизируются с самым медленным монитором и окна на более быстрых мониторах будут замедлены.
Похоже, они тестировали это только в Microsoft Windows, и не ясно, что это решение будет работать везде.
И снова многие источники сообщают мне, что makeContextCurrent() слишком медленный, чтобы иметь в подпрограмме draw().
Также похоже, что это не спецификация, совместимая с EGL. Чтобы разрешить другой поток eglSwapBuffers()
, вы должны eglMakeCurrent(NULL)
, что означает, что ваш eglSwapBuffers
теперь должен возвращать EGL_BAD_CONTEXT
.
Вопрос
Итак, мой вопрос: какой лучший способ решить проблему использования многооконного приложения с vsync? Это похоже на общую проблему, но я еще не нашел для нее удовлетворительного решения.
Похожие вопросы
Аналогично этому вопросу: Синхронизация нескольких окон OpenGL для vsync, но я хочу решение с платформой-агностиком или, по крайней мере, решение для каждой платформы.
И этот вопрос: Использование SwapBuffers() с несколькими холстами OpenGL и вертикальной синхронизацией?, но на самом деле эта проблема не имеет ничего общего с Python.