Diferenças na execução do loop e exibição de blits em SDL (Linux-Windows)

Iniciado por mhkgalvez, 25 de Novembro de 2010, 18:35

tópico anterior - próximo tópico

mhkgalvez

Olá pessoal, estou fazendo um jogo em SDL, e o professor da faculdade disse (e já observei) que o loop do jogo fica mais lento no Linux do que no Windows. E fica mesmo! Queria saber o motivo disso.

Outro problema é que no Linux há um pouco de tremedeira no movimento das imagens, no Windows não. Alguém já passou por isso e pode me ajudar!?  :-\

Abraços!  ???
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

fpissarra

mhkgalvez, eu não experimento esse tipo de comportamento com o SDL no Linux. Você poderia mostrar o fragmento de código que você está usando?

Minha experiência com SDL é bastante positiva, inclusive com OpenGL...

[]s

mhkgalvez

Certamente. O mais estranho é que sempre enfrentei isso. E meus amigo que fazem o jogo comigo também enfrentam. Olhei no Windows ontem e notei a mesma tremedeira, ao contrário do que inicialmente havia dito. Porém o loop ainda é mais rápido. Este, eu creio que não é um problema em si, mas a tremedeira sim.


Abaixo, um exemplo de código que testo:


#include <SDL/SDL.h>
#include <SDL/SDL_image.h>

#define SCREEN_W 800
#define SCREEN_H 600

SDL_Surface* my_surf;
SDL_Surface* base;
SDL_Surface* press;

SDL_Rect rect;
SDL_Rect recbase;
SDL_Rect recpress = {10, 10, 0, 0};


int incy1;


void update_positions(int max_w, int max_h)
{
rect.y += incy1;
   if(rect.y <= 0) incy1 = -incy1;
}

int main(int argc, char** argv)
{
   int move = 0;
   
   SDL_Surface* screen;
   SDL_Event event;
   int quit = 0;

Uint32 cor_fundo;

   SDL_Init(SDL_INIT_VIDEO);
   screen = SDL_SetVideoMode(SCREEN_W, SCREEN_H, 16, SDL_SWSURFACE);

my_surf = IMG_Load("yoda3d.gif");
   base = IMG_Load("nave.jpg");
   press = IMG_Load("press.jpg");
   rect.x = (SCREEN_W/2) - (my_surf->w /2);
   
   recbase.x = (SCREEN_W/2) - (base->w /2);
   recbase.y = SCREEN_H - base->h;
   
   rect.y = SCREEN_H -(base->h)/2 - my_surf->h;
   
   
cor_fundo = SDL_MapRGB(screen->format, 255, 255, 255);
   
   
   while(!quit)
   {
while(SDL_PollEvent(&event))
{
if(event.type == SDL_QUIT)
{
quit = 1;
}
if(event.type == SDL_KEYDOWN)
           {
               if (event.key.keysym.sym == SDLK_UP)
               {
                   incy1 = -1;
                   move = 1;
               }
           }
}

if (move)
       {
           update_positions(SCREEN_W, SCREEN_H);
       }
       
       if(rect.y + my_surf->h == SCREEN_H-(base->h)/2)
       {
           move = 0;
       }


SDL_FillRect(screen, NULL, cor_fundo);



       SDL_BlitSurface(base, NULL, screen, &recbase);
       SDL_BlitSurface(my_surf, NULL, screen, &rect);
       SDL_BlitSurface(press, NULL,screen, &recpress);
       
SDL_Flip(screen);
}

   SDL_Quit();

   return 0;
}


Se quiser compilar, pressione a seta para cima para fazer o boneco andar (no caso, teria que colocar três imagens disponíveis com os mesmos nomes escritos no código no mesmo diretório).
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

fpissarra

Já tentou usar Double Buffering?! E Hardware Surfaces?!

Note que SDL_Flip serve justamente para isso. Você está inicializando o modo de vídeo para SWSURFACE e sem DBLBUFFER!

[]s
Fred

fpissarra

Ahhhh... você pode ter que "lockar" a superfícia antes de modificá-la também... Isso sempre é uma boa idéia!

[]s
Fred

fpissarra

Outra coisa... Não confie que SDL_Flip() irá temporizar perfeitamente o seu loop. Placas de vídeo como a nVidia, por exemplo, podem não respeitar o vsync (flip apenas durante o retraço vertical). Então é uma boa idéia você mesmo temporizar o seu loop...

Como bonus você terá a garantia que seu código rodará na mesma velocidade em máquinas mais rápidas, e poderá medir a fatia de tempo que seu código está usando para "desenhar", aproveitando o resto do tempo para outros processamentos.

[]s
Fred


fpissarra

Se você estiver interessado, te mando um de meus primeiros projetos usando SDL e OpenGL. Ele é portável (roda no Win32 e no Linux).

Existe um "truque" para fazer com que o SDL funcione bem com o OpenGL e ele depende tando de double buffering quanto habilitar um flag específico da libSDL.

Meu e-mail é fredericopissarra@yahoo.com.br (uso esse e-mail para SPAMmers também, então não tem problema em divulgar)...
Se houver interesse mando, depois, o meu e-mail "oficial"...

[]s
Fred

mhkgalvez

 ;)
Quanto ao temporizador, eu já vou providenciar. Esse código é um exemplo simples.

Outra coisa: o que seria "lockar" a superfície?

Já testei o double buffering! Como não pensei nisso antes! :D Eu sabia que problema de flicking (acho que é esse o nome dado a esse problema) era devido a falta de buffer duplo mas não observei esse detalhe na flag de inicialização. Estou rodando aqui no Seven e funcionou muito bem.

Quanto ao seu projeto fpissarra, eu gostaria de olhar sim. Vou te mandar um e-mail.
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

fpissarra

Citação de: mhkgalvez online 28 de Novembro de 2010, 13:46
;)
Quanto ao temporizador, eu já vou providenciar. Esse código é um exemplo simples.

Outra coisa: o que seria "lockar" a superfície?

Já testei o double buffering! Como não pensei nisso antes! :D Eu sabia que problema de flicking (acho que é esse o nome dado a esse problema) era devido a falta de buffer duplo mas não observei esse detalhe na flag de inicialização. Estou rodando aqui no Seven e funcionou muito bem.

Quanto ao seu projeto fpissarra, eu gostaria de olhar sim. Vou te mandar um e-mail.

"Lockar" a superfície é usar SDL_LockSurface(). Isso é útil antes de fazer modificações na superfície (BitBlt, por exemplo)..

Quanto ao Double Buffering... pois é... além dele eu penei para achar um modo para isso funcionar com OpenGL. O código que te mandei, embora seja meio simples, contém algumas dicas valiosas... Espero que goste.

mhkgalvez

Na verdade, fugiria do escopo do projeto usar OpenGL, por hora, mas nas férias pretendo desenvolver outro jogo, com calma, aí sim tentarei esta artimanha. Na verdade, gostaria de saber quais seriam as vantagens reais (principalmente quanto a este problema de tremedeira) de seu usar OpenGL, uma vez que só estou acostumado com o SDL.

Ainda não testei seu projeto, pois estou logado no Windows e ainda não instalei as bibliotecas SDL_opengl e SDL_mixer.

"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

mhkgalvez

Olha, percebi uma coisa aqui colocando Double Buffering, Hardware Surfaces e um Timer de uns 30 milisegundos: se eu ponho velocidade de 1 pixel por ciclo na imagem em movimento, fica tudo bem. Mas se eu ponho mais que isso, fica tremendo (por exemplo, 4 ou seis pixels). O problema é que vários jogos tem alternância de velocidade em movimento. Como, então, faria esse feito? Seria simplesmente diminuindo o timer ou tem outro jeito melhor?

Código se encontra abaixo no Docs:

https://docs.google.com/leaf?id=0Bya38vmXGy29NTJiMzQyYmYtZjkzYy00ZTlmLThiNjAtODc0ZTUyOGEwZTBk&hl=pt_BR&authkey=CMyxkZAC
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

fpissarra

Citação de: mhkgalvez online 29 de Novembro de 2010, 21:11
Olha, percebi uma coisa aqui colocando Double Buffering, Hardware Surfaces e um Timer de uns 30 milisegundos: se eu ponho velocidade de 1 pixel por ciclo na imagem em movimento, fica tudo bem. Mas se eu ponho mais que isso, fica tremendo (por exemplo, 4 ou seis pixels). O problema é que vários jogos tem alternância de velocidade em movimento. Como, então, faria esse feito? Seria simplesmente diminuindo o timer ou tem outro jeito melhor?

Código se encontra abaixo no Docs:

https://docs.google.com/leaf?id=0Bya38vmXGy29NTJiMzQyYmYtZjkzYy00ZTlmLThiNjAtODc0ZTUyOGEwZTBk&hl=pt_BR&authkey=CMyxkZAC

Vou dar uma olhada.. se puder ajudar te digo o que achei...

fpissarra

Citação de: mhkgalvez online 29 de Novembro de 2010, 21:11
Olha, percebi uma coisa aqui colocando Double Buffering, Hardware Surfaces e um Timer de uns 30 milisegundos: se eu ponho velocidade de 1 pixel por ciclo na imagem em movimento, fica tudo bem. Mas se eu ponho mais que isso, fica tremendo (por exemplo, 4 ou seis pixels). O problema é que vários jogos tem alternância de velocidade em movimento. Como, então, faria esse feito? Seria simplesmente diminuindo o timer ou tem outro jeito melhor?

Código se encontra abaixo no Docs:

https://docs.google.com/leaf?id=0Bya38vmXGy29NTJiMzQyYmYtZjkzYy00ZTlmLThiNjAtODc0ZTUyOGEwZTBk&hl=pt_BR&authkey=CMyxkZAC

O problema é que você continua atualizando os quadros na máxima velocidade possível...
Me manda  a gif e os jpegs para eu fazer mais uns testezinhos e te mando o código segundo a minha revisão...

[[]]ão
Fred

mhkgalvez

"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

fpissarra

Citação de: mhkgalvez online 30 de Novembro de 2010, 22:10
Já te mandei pelo e-mail.  ::)

Ok, já que tem um monte de gente lendo esse tópico (433 até agora), vou postar as minhas observações sobre o seu código main.c aqui...

Eis as minhas modificações:

#include <assert.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>

#define SCREEN_W 800
#define SCREEN_H 600

SDL_Surface* my_surf;
SDL_Surface* base;
SDL_Surface* press;

SDL_Rect rect;
SDL_Rect recbase;
SDL_Rect recpress = {10, 10, 0, 0};

Uint32 cor_fundo;

int pause = 0;
int move = 0;   /* NOTA: Gostei do uso desse flag para decidir se vai desenhar ou não! */
int startTime;  /* NOTA: Pra que inicializar 'startTime'? */
Uint32 updateTime = 20;
int incy1 = -6; /* NOTA: Ajustei logo o valor do incremento aqui! */

void update_positions(int max_w, int max_h)
{
 rect.y += incy1;

 if (rect.y <= 0)
   incy1 = -incy1;

 /* É isso mesmo ou precisa adicionar o código abaixo? */
 // if (rect.y >= recbase.y)
 //  incy1 = -incy1;
}

/* Eu gosto de separar os pedaços de código...
  Esse aqui só faz o desenho! */
void Render(SDL_Surface *surface)
{
 SDL_FillRect(surface, NULL, cor_fundo);

 SDL_BlitSurface(base,    NULL, surface, &recbase);
 SDL_BlitSurface(my_surf, NULL, surface, &rect);
 SDL_BlitSurface(press,   NULL, surface, &recpress);

 SDL_Flip(surface);
}

int ProcessEvents(void)
{
 SDL_Event event;

 if (SDL_PollEvent(&event))
 {
   /* Pra que isso se SDLK_ESCAPE é tratado abaixo? */
   //if (event.type == SDL_QUIT)
   //  return 0;

   if (event.type == SDL_KEYDOWN)
   {
     switch (event.key.keysym.sym)
     {
       case SDLK_UP:
         if (!pause)
           move = 1;
         break;

       case SDLK_ESCAPE:
         return 0;

       case SDLK_RETURN:
         /* Não precisa de um if para fazer toggle! */
         pause = !pause;
         break;
     }
   }
 }

 return 1;
}

int main(int argc, char** argv)
{
 SDL_Surface* screen;
 Uint32 currentTime;

 SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
 screen = SDL_SetVideoMode(SCREEN_W, SCREEN_H, 32, SDL_DOUBLEBUF | SDL_HWSURFACE);

 my_surf = IMG_Load("images/yoda3d.gif");
 base    = IMG_Load("images/nave.jpg");
 press   = IMG_Load("images/press.jpg");

 /* DEBUG: Para verificar se conseguiu carregar as imagens */
 assert(my_surf != NULL);
 assert(base    != NULL);
 assert(press   != NULL);

 rect.x    = (SCREEN_W / 2) - (my_surf->w / 2);
 rect.y    = SCREEN_H - (base->h / 2) - my_surf->h;

 recbase.x = (SCREEN_W / 2) - (base->w / 2);
 recbase.y = SCREEN_H - base->h;

 cor_fundo = SDL_MapRGB(screen->format, 255, 255, 255);

 /* Desenha o primeiro quadro! */
 Render(screen);
 startTime = SDL_GetTicks();

 /* Enquanto não for para sair... */
 while (ProcessEvents())
 {
   /* Eu preferiria criar uma variável chamada 'deltaT' e armazenar a diferença
      de tempo nala, para usar em efeitos físicos.

      Note que a diferença de tempo pode ser MAIOR que 20 ms...
      Na primeira passagem, por exemplo, pode ser 22 ms, na outra 21 ms, na outra 23 ms, ...
      Assim, guardar o deltaT pode ajudar no calculo da precisão dos movimentos.
   */
   currentTime = SDL_GetTicks();

   if ((currentTime - startTime) >= updateTime)
   {
     if (!pause)
     {
       if (move)
       {
         update_positions(SCREEN_W, SCREEN_H);

         /* Só desenha se alguma coisa mudou!! */
         Render(screen);
       }

       if (rect.y + my_surf->h == SCREEN_H - (base->h) / 2)
         move = 0;
     }

     /* Se usarmos SDL_GetTicks() aqui, obteremos o tempo APÓS o desenho.
        Assim o framerate ficará instável! */
     startTime = currentTime;
   }
 }

 SDL_Quit();

 return 0;
}


Espero que ajude... Ahhh, mhkgalvez, te mandei por e-mail também...

[]s
Fred