12/07/2023
SDL2, VMware, Linux e o Modo de Mouse Relativo


Um dos meus projetos pessoais é uma engine de jogos baseado em um Renderizador de Software 3D (sobre o qual postarei em outro momento). Uma noite, enquanto eu trabalhava na implementação de uma câmera estilo FPS, percebi umas coisas estranhas quando movia o mouse.

Meu setup de desenvolvimento para esse tipo de coisa consiste em uma VM rodando Fedora XFCE Spin, onde eu codava usando CLion, compilava e executava o programa. Tudo ia muito bem até chegar ao código relacionado ao mouse.

Para a abordagem que eu estava usando, precisávamos definir o mouse para o modo de Mouse Relativo, usando a função SDL_SetRelativeMouseMode(). De acordo com a Wiki da SDL, funciona assim:

Enquanto o mouse está no modo relativo, o cursor é ocultado, a posição do mouse é restrita à janela e a SDL reportará movimento relativo contínuo do mouse mesmo se o mouse estiver na borda da janela.

Quando usado em conjunto com a função SDL_GetRelativeMouseState(), eu deveria ter os deltas do mouse desde a última vez que o input foi lido (uma vez por frame). Com esses valores, eu poderia rotacionar a câmera corretamente.

Ok, então parece que isso é exatamente o que eu precisava: o mouse não deve sair da janela e continuará a reportar movimento mesmo na borda dela. Vamos ver como fica isso no código.

void init_display()
{
    // Código de criação da janela
    // ...

    if (SDL_SetRelativeMouseMode(SDL_TRUE) != 0)
    {
        // Logar o erro e sair do programa
        return;
    }

    // ...
}

void process_input()
{
    SDL_PumpEvents();
    
    // Código de leitura de estado do teclado
    // ...
    
    int32_t x_offset, y_offset;
    SDL_GetRelativeMouseState(&x_offset, &y_offset); // Ignorando o estado dos botões por enquanto
    // Fazer algo com os valores de movimento relativo definidos pela função
    // ...
}

Mas foi aí que comecei a ter problemas. Ao rodar esse código pela primeira vez, com um pequeno toque no mouse, a câmera ficou maluca, parecendo como se a sensibilidade do mouse estava alta demais; tentei multiplicar essas duas variáveis por um fator de escala pra corrigir isso — não resolveu o problema, mas tornou o movimento mais lento, o que me fez descobrir outro problema: quando o cursor do mouse parecia atingir a borda da janela, o movimento parava completamente. Bem, algo de errado não está certo :P

Eu não tinha certeza se o código de rotação da câmera estava correto, então fiz um teste simples usando o teclado e funcionou perfeitamente. Ok, então eu tinha certeza de que os valores que eu estava obtendo do mouse estavam errados por algum motivo. Para verificar isso, coloquei um printf na função process_input():

void process_input()
{
    // ...
    
    int32_t x_offset, y_offset;
    SDL_GetRelativeMouseState(&x_offset, &y_offset); // Ignorando o estado dos botões por enquanto
    printf("Movimento do mouse: X: %d Y: %d\n", x_offset, y_offset);
    
    // ...
}

O que me retornou valores como:

Movimento do mouse: X: -34 Y: 142
Movimento do mouse: X: -34 Y: 214
Movimento do mouse: X: 0 Y: 0
Movimento do mouse: X: 0 Y: 142
Movimento do mouse: X: -34 Y: 214
Movimento do mouse: X: 0 Y: 71
Movimento do mouse: X: 0 Y: 0
Movimento do mouse: X: -34 Y: 71
Movimento do mouse: X: 0 Y: 0
Movimento do mouse: X: 0 Y: 72
Movimento do mouse: X: 0 Y: 0
Movimento do mouse: X: 0 Y: 0
Movimento do mouse: X: 0 Y: 0
Movimento do mouse: X: 0 Y: 0

Já que a função camera_rotate() que eu estava usando espera valores em graus, esses valores eram realmente muito maiores do que deveriam ser para funcionar corretamente. Além disso, aquelas linhas X:0 Y:0 eram impressas quando o mouse estava na borda da tela e ainda sendo movido.

Com uma busca rápida no Google, consegui encontrar uma issue no GitHub da SDL que parecia relacionada aos problemas que eu estava tendo. Havia uma declaração de Sam Lantinga (o criador da SDL) nela, que dizia:

Você também não pode testar o modo relativo de forma confiável em uma VM, que é uma das razões pelas quais eu ainda não cheguei a isso. :)

Hm, então o problema poderia ser a máquina virtual? Eu não tinha nenhuma máquina Linux disponível para testar o código, então acabei portando pro Windows. E funcionou perfeitamente, sem nenhum problema. Aqui está o resultado daquele printf():

Movimento do mouse: X: -2 Y: 1
Movimento do mouse: X: -3 Y: 2
Movimento do mouse: X: -3 Y: 1
Movimento do mouse: X: -2 Y: 1
Movimento do mouse: X: -3 Y: 2
Movimento do mouse: X: -3 Y: 2
Movimento do mouse: X: -2 Y: 2
Movimento do mouse: X: -2 Y: 2
Movimento do mouse: X: -2 Y: 2
Movimento do mouse: X: -2 Y: 4
Movimento do mouse: X: -2 Y: 4
Movimento do mouse: X: -1 Y: 2

Note como os valores são menores. Funciona perfeitamente com a função camera_rotate() que eu fiz, e as únicas vezes que obtive resultados X:0 Y:0 foram quando o mouse realmente estava parado.

Uns dias depois, tendo um tempo fora do trabalho, montei uma máquina Linux com algumas peças bem antigas que eu tinha guardado, instalei a mesma versão do Fedora e as ferramentas que estava usando, e compilei o código. Assim como na máquina Windows, absolutamente nenhum erro; isso me deixou bastante confiante de que o problema era de fato com a máquina virtual.

Naquela issue do GitHub que mencionei, havia uma solução alternativa que pareceu funcionar para alguém (mas não pra mim haha). Consistia em definir um Hint da SDL, assim:

void init_display()
{
    // Código de criação da janela
    // ...
    
    SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE); // a solução alternativa
    if (SDL_SetRelativeMouseMode(SDL_TRUE) != 0)
    {
        // Logar o erro e sair do programa
        return;
    }

    // ...
}

Quando tentei, não mudou em nada o comportamento do programa. Por enquanto, simplesmente desisti de fazer isso funcionar na VM e decidir codar no Windows mesmo. Talvez algum dia eu tente consertar isso novamente, mas, já que funciona em hardware real rodando Linux, não estou tão preocupado.

Se alguém tiver alguma sugestão para este problema, adoraria saber.