Несколько контекстов OpenGL, несколько окон, многопоточность и vsync

Я создаю графическое приложение для пользовательского интерфейса с использованием 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.

Ответ 1

swap-буферы (vsync заставляет это блокироваться до обновления вертикального монитора)

Нет, он не блокируется. Вызов буферного свопа может немедленно возвращаться, а не блокировать. Тем не менее, он вставляет точку синхронизации, так что выполнение команд, изменяющих задний буфер, задерживается до тех пор, пока не произойдет обмен буфера. Очередь команд OpenGL имеет ограниченную длину. Таким образом, как только очередь команд будет заполнена, дальнейшие вызовы OpenGL будут блокировать программу до тех пор, пока в очередь не будут отправлены дополнительные команды.

Также буферный обмен не является операцией OpenGL. Это операция уровня системы графики/окна и происходит независимо от контекста OpenGL. Просто взгляните на функции своп-буфера: единственный параметр, который они принимают, - это дескриптор drawable (= window). На самом деле, даже если у вас несколько контекстов OpenGL, работающих на одном вытягиваемом, вы меняете буфер только один раз; и вы можете сделать это, если контекст OpenGL не является текущим на чертеже вообще.

Итак, обычный подход:

' first do all the drawing operations
foreach w in windows:
    foreach ctx in w.contexts:
        ctx.make_current(w)
        do_opengl_stuff()
        glFlush()

' with all the drawing commands issued
' loop over all the windows and issue
' the buffer swaps.
foreach w in windows:
    w.swap_buffers()

Поскольку буферный своп не блокируется, вы можете выпустить все своп-буферы для всех окон, не задерживаясь с помощью V-Sync. Однако следующая команда рисования OpenGL, которая обращается к буферу, выпущенному для подкачки, скорее всего, остановится.

Обходной путь для этого - использование FBO, в котором происходит фактический чертеж, и комбинировать его с циклом, выполняющим размножение FBO, перед обратным буфером перед циклом буфера обмена:

' first do all the drawing operations
foreach w in windows:
    foreach ctx in w.contexts:
        ctx.make_current(w)
        glBindFramebuffer(GL_DRAW_BUFFER, ctx.master_fbo)
        do_opengl_stuff()
        glFlush()

' blit the FBOs' renderbuffers to the main back buffer
foreach w in windows:
    foreach ctx in w.contexts:
        ctx.make_current(w)
        glBindFramebuffer(GL_DRAW_BUFFER, 0)
        blit_renderbuffer_to_backbuffer(ctx.master_renderbuffer)
        glFlush()

' with all the drawing commands issued
' loop over all the windows and issue
' the buffer swaps.
foreach w in windows:
    w.swap_buffers()

Ответ 2

спасибо @andrewrk за все исследования тезисов, я personnaly делают так:

Создайте первое окно и его контекст opendl с двойным буфером. Active vsync в этом окне (swapinterval 1)

Создайте другие окна и прикрепите первый контекст с двойным буфером. Отключить vsync на другом окне (swapinterval 0)

Для каждого кадра Для инвертирования каждого окна (с включенным vsync в конце). wglMakeCurrent (hdc, commonContext);
привлечь. SwapBuffer

Таким образом, я достигаю vsync и все окна основаны на этом же vsync.

Но у меня возникла проблема без аэро: разрыв...