Thesis in short: If you build an app which measures your response time to a visual or auditive stimuli the reaction time is way longer if the app was build with SDL features compared to non-SDL implementation (independent of frames per second).
proof:
1.) AAA-game Counter Strike Global Offensive (shooter) compared Linux implementation (using SDL) with Windows (non-SDL (afaik)).
2.)Own test project
1.)CSGO LinuxSDL vs Windows
I recently discovered an aim training map which measures your reaction time.
(Yprac Aim Arena)
There a red screen turns into green and you need to press the left mouse button as fast as possible.
In windows my mean reaction time is about 200ms. At linux this game is using a SDL implementation. There my reaction time is about 260ms. (a video about this map)
60ms don’t sound much but it has a huge impact in game play. With my 60Hz display this is about 4 frames delay compared to windows non-SDL implementation.
A pro gamer (in video above) has 169ms @60Hz. According to some post I read somewhere in internet the fastest response time for visual stimuli is 120ms for humans (physical limitations).
If you remove this lower border to get possible response times it is 80ms at Windows and 140ms at Linux with SDL or 75% slower with SDL . For the pro gamer it would be 49ms vs 109ms or 122% slower. That means if he plays at Linux and I at Windows I would win (if only reaction time matters, just to show the impact of those 60ms).
(tested multiple runs with same settings and hardware (~200+fps))
2.) LinuxSDl vs WindowsSDL
For further testing I made some small program which uses SDL to test the same thing (shows red/green image a stops reaction time). At linux I got about the same 260ms again, for windows 238ms. So about 40ms slower than in game. I added some auditive stimuli with SDL_mixer:
Mix_PlayChannel(-1, sound, 0);
Nothing changed, still around 240ms reaction time at windows. For tests I exchanged it with the windows only function (windows.h)
PlaySound(TEXT("sound.wav"), NULL, SND_FILENAME | SND_ASYNC);
With this I got a mean reaction time of 200ms again. Same as at the in game test from 1.).
Conclusion: The SDL output (picture and sound) is delayed by 40-60ms or by 2-4 frames or 75% slower.
(Linux Mint 18.3 and Windows 7, 60Hz screen, nvidia GPU, intel CPU were used)
Test code
Here is my crappy copy&paste code (sry only did SDL1 long time ago).
Maybe I did done some crucial mistakes in coding which results in this delay instead of SDL. But this would still not explain the big difference in CSGO.
//clang -std=c++14 ./react.cpp -lSDL2 -lstdc++
//only for windows (for playSound)
#ifdef _WIN32
#include "stdafx.h"
#include <windows.h>
#endif
#include <SDL2/SDL.h>
#include <stdio.h>
#include <ctime>
#include <iostream>
#include <chrono>
void runGame(SDL_Window* window) {
SDL_AudioSpec wav_spec;
Uint32 wav_length;
Uint8 *wav_buffer;
SDL_AudioSpec desired;
SDL_AudioSpec obtained;
SDL_zero(desired);
desired.freq = 44100;
desired.format = AUDIO_S16;
desired.channels = 2;
desired.samples = 4096;
desired.callback = NULL;
SDL_AudioDeviceID deviceId = 0;
if (SDL_LoadWAV("sound.wav", &wav_spec, &wav_buffer, &wav_length))
{
deviceId = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (deviceId)
{
SDL_PauseAudioDevice(deviceId, 0);
int success = SDL_QueueAudio(deviceId, wav_buffer, wav_length);
if (success < 0)
SDL_ShowSimpleMessageBox(0, "Error", "Failed to queue audio", NULL);
}
else
SDL_ShowSimpleMessageBox(0, "Error", "Audio driver failed to initialize", NULL);
}
else
SDL_ShowSimpleMessageBox(0, "Error", "wav failed to load", NULL);
std::srand(std::time(nullptr));
unsigned long long int delay, delaySum;
std::chrono::steady_clock::time_point start;
std::chrono::steady_clock::time_point end;
SDL_Surface *screenSurface = SDL_GetWindowSurface(window);
SDL_Event event;
SDL_DisplayMode DM;
SDL_GetCurrentDisplayMode(0, &DM);
auto Width = DM.w;
auto Height = DM.h;
SDL_Rect rect;
rect.x = (int)(Width / 2 - Width * 0.05);
rect.y = (int)(Height / 2 - Height * 0.05);
rect.w = (int)(Width * 0.1);
rect.h = (int)(Height * 0.1);
int counter = 0;
delaySum = 0;
int soundMode = 0;
while (1) {
start = std::chrono::steady_clock::now();
end = std::chrono::steady_clock::now();
//windows int did only up to 32k oO
//-> negative results possible -> uns long long int
delay = (( ((unsigned long long int)std::rand()) * 641234+((unsigned long long int)std::rand())*32000+ std::rand()) % 4000000) + 2000000;
SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0xAA, 0x11, 0x11));
SDL_FillRect(screenSurface, &rect, SDL_MapRGB(screenSurface->format, 0, 0, 0));
SDL_UpdateWindowSurface(window);
int soundModeBF = soundMode;
while ((unsigned long long int)std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()<delay) {
SDL_Delay(1);
end = std::chrono::steady_clock::now();
SDL_PollEvent(&event);
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { //right mouse button to reset counter
counter = 0;
delaySum = 0;
}
if (event.key.keysym.sym == SDLK_ESCAPE) //exit with Esc
return;
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_s && soundModeBF== soundMode) { //s to toggle sound on/off
soundMode = (soundMode+1) % 2;
counter = 0;
delaySum = 0;
}
}
SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0x11, 0xAA, 0x11));
SDL_FillRect(screenSurface, &rect, SDL_MapRGB(screenSurface->format, 0, 0, 0));
SDL_UpdateWindowSurface(window);
if(soundMode == 1){
/SDL_QueueAudio(deviceId, wav_buffer, wav_length);
//PlaySound((LPCTSTR)SND_ALIAS_SYSTEMQUESTION, NULL, SND_ALIAS_ID | SND_ASYNC); //only for windows
//PlaySound(TEXT("sound.wav"), NULL, SND_FILENAME | SND_ASYNC);
}
//else if (soundMode==2) only one of those above work at same time, fix?
start = std::chrono::steady_clock::now();
bool pressed = false;
bool exit = false;
soundModeBF = soundMode;
while (!pressed && !exit) {
SDL_Delay(1);
while (SDL_PollEvent(&event)) {
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_s && soundModeBF == soundMode) {
soundMode = (soundMode + 1) % 2;
counter = 0;
delaySum = 0;
}
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
pressed = true;
break;
}
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) {
counter = 0;
delaySum = 0;
break;
}
if (event.key.keysym.sym == SDLK_ESCAPE) {
exit = true;
break;
}
}
}
if (exit)
break;
end = std::chrono::steady_clock::now();
delay = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
counter++;
delaySum += delay;
std::cout << "delay "
<< (delay / 1000)
<< "ms. mean "<<(delaySum/(1000*counter)) << " sound "<< soundMode << " trials " << counter << " \n";
}
SDL_CloseAudioDevice(deviceId);
}
int main(int argc, char* args[])
{
SDL_Window* window = NULL;
SDL_Surface* screenSurface = NULL;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
fprintf(stderr, "could not initialize sdl2: %s\n", SDL_GetError());
return 1;
}
SDL_DisplayMode DM;
SDL_GetCurrentDisplayMode(0, &DM);
auto Width = DM.w;
auto Height = DM.h;
window = SDL_CreateWindow(
"reaction test",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
Width, Height,
SDL_WINDOW_SHOWN
);
if (window == NULL) {
fprintf(stderr, "could not create window: %s\n", SDL_GetError());
return 1;
}
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
screenSurface = SDL_GetWindowSurface(window);
runGame(window);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Is this a known issue?
Any way to fix it?
What are your thoughts about this topic?
What are your results in those tests? It’s only me who has those delays?