Как можно оптимизировать эту функцию?(Использует почти всю вычислительную мощность) - PullRequest
0 голосов
/ 28 августа 2011

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

По сути, в моей игре пока что мало что происходит, только некоторые базовые движенияи фоновый рисунок.Когда я переключился на OpenGL, кажется, что он way слишком быстро.Число моих кадров в секунду превышает 2000, и эта функция потребляет большую часть вычислительной мощности.

Что интересно, программа в своей версии SDL использует 100% ЦП, но работает без сбоев, в то время как версия OpenGL использует только около 40% - 60% ЦП, но, кажется, нагружает мою видеокарту таким образом, что весь мой рабочий стол перестает отвечать на запросы.Плохо.

Это не слишком сложная функция, она создает фоновый тайл 1024x1024 в соответствии с координатами X и Y игрока, чтобы создать впечатление движения, в то время как само изображение игрока остается заблокированным в центре.Поскольку это небольшая плитка для большого экрана, я должен рендерить ее несколько раз, чтобы соединить плитки вместе для получения полного фона.Два цикла for в приведенном ниже коде повторяются вместе 12 раз, поэтому я понимаю, почему это неэффективно при вызове 2000 раз в секунду.

Итак, чтобы перейти к сути, это злодей:

void render_background(game_t *game)
{
    int bgw;
    int bgh;

    int x, y;

    glBindTexture(GL_TEXTURE_2D, game->art_background);
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH,  &bgw);
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &bgh);

    glBegin(GL_QUADS);

    /*
     * Start one background tile too early and end one too late
     * so the player can not outrun the background
     */
    for (x = -bgw; x < root->w + bgw; x += bgw)
    {
        for (y = -bgh; y < root->h + bgh; y += bgh)
        {
            /* Offsets */
            int ox = x + (int)game->player->x % bgw;
            int oy = y + (int)game->player->y % bgh;

            /* Top Left */
            glTexCoord2f(0, 0);
            glVertex3f(ox, oy, 0);

            /* Top Right */
            glTexCoord2f(1, 0);
            glVertex3f(ox + bgw, oy, 0);

            /* Bottom Right */
            glTexCoord2f(1, 1);
            glVertex3f(ox + bgw, oy + bgh, 0);

            /* Bottom Left */
            glTexCoord2f(0, 1);
            glVertex3f(ox, oy + bgh, 0);
        }
    }

    glEnd();
}

Если я искусственно ограничиваю скорость, набрав SDL_Delay(1) в игровом цикле, я урежу FPS до ~ 660 ± 20, я не получу «перебор производительности».Но я сомневаюсь, что это правильный путь для этого.

Ради завершения, это мои общие функции рендеринга и игрового цикла:

void game_main()
{
    long current_ticks = 0;
    long elapsed_ticks;
    long last_ticks = SDL_GetTicks();

    game_t game;
    object_t player;

    if (init_game(&game) != 0)
        return;

    init_player(&player);
    game.player = &player;

    /* game_init() */
    while (!game.quit)
    {
        /* Update number of ticks since last loop */
        current_ticks = SDL_GetTicks();
        elapsed_ticks = current_ticks - last_ticks;

        last_ticks = current_ticks;

        game_handle_inputs(elapsed_ticks, &game);
        game_update(elapsed_ticks, &game);

        game_render(elapsed_ticks, &game);

        /* Lagging stops if I enable this */
        /* SDL_Delay(1); */
    }

    cleanup_game(&game);


    return;
}

void game_render(long elapsed_ticks, game_t *game)
{
    game->tick_counter += elapsed_ticks;

    if (game->tick_counter >= 1000)
    {
        game->fps = game->frame_counter;
        game->tick_counter = 0;
        game->frame_counter = 0;

        printf("FPS: %d\n", game->fps);
    }

    render_background(game);
    render_objects(game);

    SDL_GL_SwapBuffers();
    game->frame_counter++;

    return;
}

Согласно профилированию gprof,даже когда я ограничиваю выполнение с помощью SDL_Delay(), оно все равно тратит около 50% времени на рендеринг моего фона.

Ответы [ 3 ]

6 голосов
/ 28 августа 2011

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

3 голосов
/ 29 августа 2011

Прежде всего, вам не нужно визуализировать плитку x * y раз - вы можете визуализировать ее один раз для всей области, которую она должна покрыть, и использовать GL_REPEAT, чтобы OpenGL покрыл всю область ею.Все, что вам нужно сделать, это вычислить правильные координаты текстуры один раз, чтобы плитка не была искажена (растянута).Чтобы заставить его казаться движущимся, увеличивайте координаты текстуры с небольшим запасом в каждом кадре.

Теперь перейдем к ограничению скорости.То, что вы хотите сделать, это не просто подключить вызов sleep (), а измерить время, необходимое для рендеринга одного полного кадра:

function FrameCap (time_t desiredFrameTime, time_t actualFrameTime)
{
   time_t delay = 1000 / desiredFrameTime;
   if (desiredFrameTime > actualFrameTime)
      sleep (desiredFrameTime - actualFrameTime); // there is a small imprecision here
}

time_t startTime = (time_t) SDL_GetTicks ();
// render frame
FrameCap ((time_t) SDL_GetTicks () - startTime);

Есть способы сделать это более точным (например,использование функций счетчика производительности в Windows 7 или использование микросекундного разрешения в Linux), но я думаю, что вы поняли основную идею.Этот подход также имеет преимущество, заключающееся в том, что он не зависит от драйвера и, в отличие от подключения к V-Sync, обеспечивает произвольную частоту кадров.

1 голос
/ 04 сентября 2011

При 2000 FPS требуется всего 0,5 мс для рендеринга всего кадра.Если вы хотите получить 60 FPS, то каждый кадр должен занять около 16 мс.Для этого сначала просмотрите ваш кадр (около 0,5 мс), затем используйте SDL_Delay(), чтобы использовать оставшиеся 16 мс.

Кроме того, если вы заинтересованы в профилировании вашего кода (который не являетсянеобходимо, если вы получаете 2000 FPS!), тогда вы можете использовать Таймеры высокого разрешения .Таким образом, вы можете точно сказать, сколько времени занимает любой блок кода, а не только сколько времени тратит на это ваша программа.

...