Избегайте ожидания буферов подкачки

#c #winapi #opengl

#c #winapi #opengl

Вопрос:

Я обнаружил, что буферы подкачки в OpenGL будут заняты-ждать до тех пор, пока графическая карта не завершит рендеринг или если она ожидает V-Sync.

Для меня это проблема, потому что я не хочу тратить 100% ядра процессора, просто ожидая завершения работы карты. Я не пишу игру, поэтому я не могу использовать циклы процессора для чего-либо более продуктивного, я просто хочу передать их какому-нибудь другому процессу в операционной системе.

Я нашел функции обратного вызова, такие как glutTimerFunc и glutIdleFunc, которые могли бы сработать для меня, но я не хочу использовать glut. Тем не менее, glut должен каким-то образом использовать обычные функции gl для этого, верно?

Существует ли какая-либо функция, такая как «glReadyToSwap» или аналогичная? В этом случае я мог бы проверять это каждую миллисекунду или около того и определять, следует ли мне подождать еще немного или выполнить подкачку. Я мог бы также представить, возможно, пропустить буферы подкачки и написать свою собственную аналогичную функцию, которая не занята-подождите, если кто-нибудь может указать мне правильное направление.

Ответ №1:

SwapBuffers не занят ожиданием, он просто блокирует ваш поток в контексте драйвера, что заставляет Windows неправильно вычислять загрузку ЦП: Windows вычисляет загрузку ЦП, определяя, сколько процессорного времени получает процесс ожидания сколько времени программы не тратят в контексте драйвера. Буферы подкачки будут блокироваться в контексте драйвера, и ваша программа, очевидно, отнимает это процессорное время у процесса ожидания. Но ваш процессор буквально ничего не делает за это время, планировщик счастливо ждет, чтобы передать время другим процессам. Простаивающий процесс OTOH не делает ничего другого, кроме как немедленно передает свое время остальной части системы, поэтому планировщик возвращается обратно в ваш процесс, который блокирует в драйвере то, что Windows считает «засоряющим процессор». Если вы измеряете фактическое энергопотребление или тепловыделение, для простой программы OpenGL это значение останется довольно низким.

Это раздражающее поведение на самом деле является часто задаваемым вопросом OpenGL!

Просто создайте дополнительные потоки для параллельной обработки данных. Держите OpenGL в одном потоке, обработку данных — в другом. Если вы хотите снизить заявленную загрузку процессора, добавление Sleep (0) или Sleep (1) после буферов подкачки сделает свое дело. Режим ожидания (1) заставит ваш процесс потратить немного времени на блокировку в пользовательском контексте, поэтому процесс ожидания получает больше времени, что приведет к выравниванию чисел. Если вы не хотите переходить в режим ожидания, вы можете сделать следующее:

 const float time_margin = ... // some margin
float display_refresh_period; // something like 1./60. or so.

void render(){

    float rendertime_start = get_time();

    render_scene();
    glFinish();

    float rendertime_finish = get_time();
    float time_to_finish = rendertime_finish - rendertime_start;

    float time_rest = fmod(render_finish - time_margin, display_refresh_period);
    sleep(time_rest);
    SwapBuffers();
 }
  

В своих программах я использую такой режим синхронизации, но по другой причине: я разрешаю буферам подкачки блокироваться без каких-либо вспомогательных спящих режимов, однако я даю некоторым другим рабочим потокам примерно в это же время что-то делать на графическом процессоре через общий контекст (например, обновлять текстуры), и у меня запущен сборщик мусора. На самом деле нет необходимости точно определять время, но рабочие потоки, завершающиеся непосредственно перед возвратом SwapBuffers, позволяют практически сразу начать рендеринг следующего кадра, поскольку большинство мьютексов к тому времени уже разблокированы.

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

1. Ну, специфика того, как происходит ожидание, для меня не так важна. Что я могу сказать, так это то, что вентиляторы в компьютере ускоряются, и я предполагаю, что они реагируют на нагрев, что означает, что процессор РАБОТАЕТ на 100%, поскольку он нагревается. Даже если этого не произошло, и это просто Windows думает, что она работает на 100%, это все равно означает, что Windows не выделит никакого процессорного времени ни одному другому процессу, потому что она думает, что оно уже использовано на 100%! Как бы вы на это ни смотрели, вы тратите впустую процессорное время, которое могло бы быть использовано чем-то другим, либо сжигая его, либо переводя на холостой ход.

2. Вы пробовали запускать другую программу, которая также потребляет много процессорного времени?

3. ^^ Я имел в виду вместе с вашей программой OpenGL.

4. Я думаю, что у меня достаточно компьютерных знаний, чтобы знать, когда процессор работает без сбоев. Что, я думаю, происходит в вашей программе, так это 1) рендеринг, 2) застревание в glFinish до тех пор, пока карта не будет готова 3) отсыпание оставшейся 1/60-й секунды 4) Буферы подкачки. Однако в # 2 вы БУДЕТЕ заняты ожиданием, потому что, насколько я знаю, glFinish не зависит от платформы и не может использовать какие-либо другие методы, кроме busy-waiting для остановки выполнения. В игре или подобном ей это не так важно, вы можете делать полезные вещи между # 1 и # 2, но в обычной программе Windows вы не хотите сжигать 100% процессора на одном ядре только для того, чтобы показать какое-то 3D-изображение.

5. Должны ли загруженные циклы ожидания быть энергоемкими? ALU, FPU и т.д. Не задействованы. Ожидание с занятостью является расточительным, но оно не должно быть особенно горячим.

Ответ №2:

Хотя eglSwapBuffers ожидание не занято, законное использование для неблокирующего eglSwapBuffers интерфейса заключается в том, чтобы иметь более отзывчивый поток GUI, который может прослушивать пользовательские входные или выходные сигналы вместо ожидания, пока OpenGL завершит замену буферов. У меня есть решение половины этой проблемы. Сначала в вашем основном цикле вы буферизуете свои команды OpenGL для выполнения в вашем замененном буфере. Затем вы проводите опрос объекта sync, чтобы узнать, завершили ли ваши команды выполнение в вашем замененном буфере. Затем вы можете поменять местами буферы, если команды завершили выполнение. К сожалению, это решение только асинхронно ожидает завершения выполнения команд в вашем выгруженном буфере и не асинхронно ожидает vsync. Вот код:

  void process_gpu_stuff(struct gpu_context *gpu_context)
 {
     int errnum = 0;

     switch (gpu_context->state) {
     case BUFFER_COMMANDS:
         glDeleteSync(gpu_context->sync_object);
         gpu_context->sync_object = 0;

         real_draw(gpu_context);
         glFlush();

         gpu_context->sync_object = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
         if (0 == gpu_context->sync_object) {
             errnum = get_gl_error();
             break;
         }
         gpu_context->state = SWAP_BUFFERS;
         break;

     case SWAP_BUFFERS:
         /* Poll to see if the buffer is ready for swapping, if
          * it is not in ready we can listen for updates in the
          * meanwhile. */
         switch (glClientWaitSync(gpu_context->sync_object, 0, 1000U)) {
         case GL_ALREADY_SIGNALED:
         case GL_CONDITION_SATISFIED:
             if (EGL_FALSE == eglSwapBuffers(display, surface)) {
                 errnum = get_egl_error();
                 break;
             }
             gpu_context->state = BUFFER_COMMANDS;
             break;

         case GL_TIMEOUT_EXPIRED:
             /* Do nothing. */
             break;

         case GL_WAIT_FAILED:
             errnum = get_gl_error();
             break;
         }
         break;
     }
 }
  

Ответ №3:

популярный ответ здесь неверен. windows не сообщает об использовании процессора «неправильно», lol. opengl с включенной vsync даже при рендеринге пустого экрана фактически сжигает 100% 1 потока вашего процессора. (вы можете проверить температуру своего процессора)

но решение простое. просто вызывайте DwmFlush(); до или после буферов подкачки