I'm currently at my wits end with a problem while trying to optimize/smooth the refresh rate of my game.
The problem is that every once in a while the applications main thread will pause for ~15 milliseconds while waiting on a resource from what I assume is the opengl worker thread (as it is a thread created around when the sf::RenderWindow is, and is not created by my code directly).
In Visual Studio's Concurrency Viewer these pauses looks like this:Where 5728 Worker Thread is the (I think) opengl thread, 8996 Main Thread is my event loop + render thread, and 2076 Worker Thread is my logic thread.
The data and stack on the Main Thread looks like this:Category = Synchronization
Api = WaitForSingleObject
Delay = 13.8911 ms
Unblocked by thread 5728; click 'Unblocking Stack' for details.
kernel32.dll!_WaitForSingleObject@8+0x12
nvoglv32.dl!0x8588ff
nvoglv32.dl!0x851cde
nvoglv32.dl!0x851bcd
opengl32.dll!___DrvSwapBuffers@8+0x37
opengl32.dll!_wglSwapBuffers@4+0x6f
gdi32.dll!_SwapBuffers@4+0x25
sfml-window-2.dll!0x4e24
kami.exe!kami::Client::Draw+0x251 - d:\documents\programming\kami\kami\src\base\client.cpp(718, 718)
kami.exe!kami::Client::Run+0xbb7 - d:\documents\programming\kami\kami\src\base\client.cpp(517, 517)
kami.exe!kami::Game::RunNetGame+0xe3 - d:\documents\programming\kami\kami\src\base\game.cpp(357, 357)
kami.exe!kami::Game::Play+0x12d8 - d:\documents\programming\kami\kami\src\base\game.cpp(324, 324)
kami.exe!_main+0x38b - d:\documents\programming\kami\kami\src\main.cpp(114, 114)
kami.exe!_WinMain@16+0x16
The unblocking stack looks like this:Thread 8996 was unblocked by thread 5728
The unblocking call stack follows:
ntoskrnl.exe! ?? ::FNODOBFM::`string'+0xb49b
ntoskrnl.exe!NtSetEvent+0x90
ntoskrnl.exe!KiSystemServiceCopyEnd+0x13
wow64cpu.dll!CpupSyscallStub+0x9
wow64cpu.dll!Thunk0Arg+0x5
wow64.dll!RunCpuSimulation+0xa
wow64.dll!Wow64LdrpInitialize+0x42a
ntdll.dll! ?? ::FNODOBFM::`string'+0x6a77
ntdll.dll!LdrInitializeThunk+0xe
ntdll.dll!_NtSetEvent@8+0x15
kernelbase.dll!_SetEvent@4+0x10
nvoglv32.dl!0x8588dc
nvoglv32.dl!0x865742
nvoglv32.dl!0x711504
nvoglv32.dl!0x71136c
nvoglv32.dl!0x85a6cc
From looking at these stacks it seems to me like the problem is somehow related to the SwapBuffers call relying on a resource which can become blocked by the (supposed) opengl thread, but I simply don't know enough about that level of opengl to have any idea what I'm dealing with.
I have tried disabling nearly all of my own code (the entire logic thread + all of the event handling in the main thread), and still couldn't see any change in these pauses.
I have tried using both setFramerateLimit() and setVerticalSyncEnabled() to limit the execution of the main thread, hoping that the waits would fall naturally into the non-executing time of the main thread, however both of these make the issue much more frequent and noticable.
I also tested three of my older SFML projects in SFML 1.6, 2.0, and 2.1 respectively and noticed that they all shared the same problem, even though I had not noticed it before.
I am currently working on a minimal code example of this issue, but wanted to post this first in case it is a simple problem that I'm just missing for some reason.
Edit:I have created two very minimal code examples which I have found to cause this issue, one with threaded logic and one without.
The threaded logic example causes the issue more frequently.
Without threaded logic:void main()
{
sf::Vector2i screenSize(1280, 720);
sf::CircleShape circle;
sf::RenderWindow window;
window.create(sf::VideoMode(screenSize.x, screenSize.y), "test");
circle = sf::CircleShape(40);
circle.setOrigin(20, 20);
circle.setFillColor(sf::Color::Black);
circle.setOutlineColor(sf::Color::White);
circle.setOutlineThickness(2.0f);
float circlePosMod = 0.0f, circleDistance = 200.0f, timestepMod = 2.0f;
sf::Clock updateClock;
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
//Update start
float updateTime = updateClock.restart().asMicroseconds() / 1000000.0f;
circlePosMod += updateTime * timestepMod;
circle.setPosition((cos(circlePosMod) * circleDistance) + (float)screenSize.x / 2.0f, (sin(circlePosMod) * circleDistance) + (float)screenSize.y / 2.0f);
//Update end
//Draw start
window.clear();
//Draw stuff here
window.draw(circle);
window.display();
//Draw end
}
}
With threaded logic:sf::Vector2i screenSize(1280, 720);
sf::CircleShape circle;
bool endLogic = false;
void logicThread()
{
circle = sf::CircleShape(40);
circle.setOrigin(20, 20);
circle.setFillColor(sf::Color::Black);
circle.setOutlineColor(sf::Color::White);
circle.setOutlineThickness(2.0f);
float circlePosMod = 0.0f, circleDistance = 200.0f, timestepMod = 2.0f;
sf::Clock updateClock;
while (!endLogic)
{
//Update start
float updateTime = updateClock.restart().asMicroseconds() / 1000000.0f;
circlePosMod += updateTime * timestepMod;
circle.setPosition((cos(circlePosMod) * circleDistance) + (float)screenSize.x / 2.0f, (sin(circlePosMod) * circleDistance) + (float)screenSize.y / 2.0f);
//Update end
//Don't lock up the machine
sf::sleep(sf::microseconds(500));
}
}
void main()
{
sf::RenderWindow window;
window.create(sf::VideoMode(screenSize.x, screenSize.y), "test");
//window.setVerticalSyncEnabled(true);
endLogic = false;
sf::Thread thread(&logicThread);
thread.launch();
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
//Draw start
window.clear();
//Draw stuff here
window.draw(circle);
window.display();
//Draw end
}
endLogic = true;
thread.wait();
}