游戏中的VSYNC周期是否应该花费固定的时间? [英] Shouldn't a VSYNC'ed cycle in a game take a constant amount of time?

查看:85
本文介绍了游戏中的VSYNC周期是否应该花费固定的时间?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在Windows上使用SDL用C编写一个小蛇游戏.在每帧上,我将每个正方形移动恒定数量的像素(例如2个像素).

I am writing a little snake game in C, using SDL, on Windows. On each frame I move each square a constant amount of pixels (e.g. 2 pixels).

只要启用了Vsync,运动就非常平稳.但是,我试图在没有Vsync的情况下达到相同的平滑度,只是为了学习如何工作,并且经过大量的努力,我设法通过使用Performance Counter将周期缩短到几乎完美的0.016667秒(此处为60Hz监视器).测量重复的SDL_Delay(1),直到一个循环的0.014秒+一个空循环(最高0.016667s).我猜这可以很好地工作(因此,SDL_Delay(1)所花费的时间不多),因为每1000帧中有1帧偏离了0.1ms,这应该不会引起注意.

As long as I have Vsync enabled, the movement is very smooth. However, I was trying to achieve the same smoothness without Vsync, just to learn how things work, and after a lot of struggle I have managed to time the cycles down to almost a perfect 0.016667 seconds (60Hz monitor here) by using the Performance Counter to measure repeated SDL_Delay(1) until 0.014 seconds of a cycle + an empty loop up to 0.016667s. This works great (so it's not a matter of SDL_Delay(1) taking more than it should) I guess, because 1 in 1000 frames is off by 0.1ms, and this should not be noticeable.

但是有时运动仍然很不稳定-好像它在一帧内根本不动,然后在下一帧追赶上一倍.我认为这归结为以下事实:游戏会在应有的时候进行更新和渲染,但是显示器上的实际显示要早于应有的时间,然后要比应有的时间晚一些.

But the movement is still jerky sometimes - as if it doesn't move at all for one frame and then catches up double on the next. This I think boils down to the fact that the game is updating and rendering when it should, but the actual display on the monitor takes place a little sooner than it should, and then a little later than it should.

所以我做了一些时间测量,结果对我来说是非常令人惊讶的-参见附图.启用Vsync后,所测量的周期时间到处都是-比非vsync版本差很多-而且我期望有一个完美的恒定时间.问题是:物理监视器连续两张连续图纸之间的实际时间不是恒定的吗?因为如果是这种情况,没有某种反馈就无法实现我正在尝试的方法上一次绘制的确切时间...

So I did some time measurements and the results are very surprising to me - see attached graphs. With Vsync enabled, the measured cycle times are all over the place - much much worse than the non-vsynced version - and I was expecting a perfect constant time. So the question is: Is the actual time between 2 consecutive drawings to the physical monitor not constant? Because if that is the case there is no way you can achieve what I am trying without some sort of feedback of when exactly the last drawing took place...

P.S.更新和渲染非常非常快-例如如果周期不受限制,则每秒可以运行数百万次.因此,这不会减慢周期.

P.S. The update and rendering are very very fast - e.g. if the cycle is not limited it can go millions of times per second. So that is not slowing the cycle down.

#include <SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <windows.h>

typedef enum {false, true} bool;

#define NONE 0
#define QUIT 1
#define LEFT 2
#define RIGHT 3
#define UP 4
#define DOWN 5

typedef struct Color
{
    char R, G, B, A;
}Color;

typedef struct Square
{
    int x, y, side, dir;
    Color color;
}Square;


const int WINDOW_W = 600;
const int WINDOW_H = 800;
const int SQ_SIDE = 48;
const Color SQ_COLOR = {0, 0, 0x99, 0xFF};
const Color BG_COLOR = {0xFF, 0xFF, 0xFF, 0xFF};

SDL_Window* g_window = NULL;
SDL_Renderer* g_renderer = NULL;

float speed = 120; /* pixels per second */
int display_refresh_rate;

bool vsync_enabled = false; /* enable/disable vsync from here */

bool clear_screen(void);
void draw_rect(int x, int y, int width, int height, Color color);
void update_square_position(Square *square);
int process_input_events();


int main(int argc, char* args[])
{

    SDL_Init(SDL_INIT_VIDEO);
    g_window = SDL_CreateWindow("Snake", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_W, WINDOW_H, SDL_WINDOW_SHOWN);

    int renderer_flags = SDL_RENDERER_ACCELERATED;
    if(vsync_enabled) renderer_flags |= SDL_RENDERER_PRESENTVSYNC;

    g_renderer = SDL_CreateRenderer(g_window, -1, renderer_flags);
    SDL_SetRenderDrawBlendMode(g_renderer, SDL_BLENDMODE_BLEND);

    int display_index;
    SDL_DisplayMode display_mode;
    display_index = SDL_GetWindowDisplayIndex(g_window);
    SDL_GetDesktopDisplayMode(display_index, &display_mode);

    display_refresh_rate = display_mode.refresh_rate;
    float target_cycle_time = 1.0f / display_refresh_rate;

    clear_screen();

    unsigned long long tick_now = SDL_GetPerformanceCounter();
    unsigned long long last_render_tick = tick_now;

    float time_from_last_render = 0.0f;
    float render_times_array[3601];

    float perf_freq = (float)SDL_GetPerformanceFrequency();
    timeBeginPeriod(1);

    Square *square = (Square*) malloc(sizeof(Square));

    square->x = 50;
    square->y = 50;
    square->color = SQ_COLOR;
    square->side = SQ_SIDE;
    square->dir = RIGHT;

    int i = 0;

    while(1)
    {
        tick_now = SDL_GetPerformanceCounter();
        time_from_last_render = (tick_now - last_render_tick) / perf_freq;

        if(time_from_last_render < target_cycle_time - 0.002f)
            SDL_Delay(1);


        if(vsync_enabled || (time_from_last_render >= target_cycle_time))
        {
            if(process_input_events()== QUIT) break;

            update_square_position(square);

            clear_screen();
            draw_rect(square->x, square->y, SQ_SIDE, SQ_SIDE, square->color);
            SDL_RenderPresent(g_renderer);

            last_render_tick = tick_now;

            /* save 3600 timestamps and print them */
            render_times_array[i++] = time_from_last_render;
            if(i >= 3600)
            {
                clear_screen();
                int j;
                for(j = 0; j < i; j++)
                    printf("%f\n", render_times_array[j]);

                i=0;
                break;
            }
        }
    }

    SDL_DestroyWindow(g_window);
    SDL_DestroyRenderer(g_renderer);
    SDL_Quit();

    return 0;
}

bool clear_screen(void)
{
    /* SDL functions return 0 on success! */
    if(SDL_SetRenderDrawColor(g_renderer, BG_COLOR.R, BG_COLOR.G, BG_COLOR.B, BG_COLOR.A))
        return false;
    else if(SDL_RenderClear(g_renderer))
        return false;

    return true;
}

void draw_rect(int x, int y, int width, int height, Color color)
{
    SDL_Rect rect = {x, y, width, height};
    SDL_SetRenderDrawColor(g_renderer, color.R, color.G, color.B, color.A);
    SDL_RenderFillRect(g_renderer, &rect);
}

void update_square_position(Square *square)
{
    int step = speed / display_refresh_rate;

    if(square->dir == RIGHT)
    {
        square->x += step;
        if(square->x > WINDOW_W - 100) square->dir = DOWN;
    }

    if(square->dir == LEFT)
    {
        square->x -= step;
        if(square->x < 50) square->dir = UP;
    }

    if(square->dir == DOWN)
    {
        square->y += step;
        if(square->y > WINDOW_H - 100) square->dir = LEFT;
    }

    if(square->dir == UP)
    {
        square->y -= step;
        if(square->y < 50) square->dir = RIGHT;
    }
}

int process_input_events()
{
    SDL_Event event_handler;

    while(SDL_PollEvent(&event_handler) != 0)
    {
        if(event_handler.type == SDL_QUIT)
            return QUIT;
    }
    return NONE;
}

推荐答案

对于以下问题:两个连续的物理图纸到物理显示器之间的实际时间不是恒定的吗?答案是,因为它取决于需要呈现的内容.

For the question: Is the actual time between 2 consecutive drawings to the physical monitor not constant? the answer is NO because it will depend on what it needs to be render.

您说的是在每个帧上移动正方形,这是一个错误,因为如您所见,帧不是固定时间显示的.您需要以独立于帧速率的方式对此行为(以及您的所有游戏逻辑)进行编码,而不是每帧移动 N 个像素,而是需要根据时间设置速度,是否为每3ms 2像素,然后每帧您应该计算帧之间的经过时间(Google用于SDL),并用该经过的时间来移动对象.例如(用伪代码).

You are saying that you move your squares on each frame and thats an error because as you see, the frames aren't displayed in constant time. You need to code this behavior (and all your game logic) in a framerate independent manner, instead of moving N pixels each frame, you need to set a speed based in time, could it be 2pixels each 3ms and then each frame you should compute the elapsed time between frames (google that for SDL) and with that elapsed time you could move your objects. Eg (in pseudo code).

float speed = 10;
float timeFrameInMS = 2;

onNewFrame(){
    float elapsedTimeInMS = ...getting the elapsed time.. // lets say it is .5ms
    float movePercentage = elapsedTimeInMS/timeFrameInMS // .25
    float distanceToMove = speed*movePercentage.
    /*Move objects*/
}

您需要开始思考时间而不是思考时间,因为运行游戏的设备有时可能会冻结".例如,帧之间的时间可能是1秒(发生).也可能是另一种方式,可能有一个设备以您正在开发的帧速率的两倍运行,如果您将逻辑基于帧,那么该设备将以两倍的速度提供所有功能.

You need to start thinking in time instead of frames, because the device running the game could at some moment have a "freeze" and the time between frames could be 1 second for example (it happens). It could also be the other way, there could be a device running at twice the framerate you are developing, if you base your logic on frames then this device will have everything at double the speed.

这是一本有关该主题以及有关游戏开发的其他基本技巧的书:

Here is a book on the topic and with other basic tecniques for game development: Game Programming Algorithms and Techniques: A Platform-Agnostic Approach

这篇关于游戏中的VSYNC周期是否应该花费固定的时间?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆