Vsync fenêtré fiable avec OpenGL sur Windows?

RÉSUMÉ

Il semble que vsync avec OpenGL est cassé sous Windows en mode fenêtré. J’ai essayé différentes API (SDL, glfw, SFML), le tout avec le même résultat: bien que le framerate soit limité (et constamment autour de 16-17 ms selon les mesures de CPU sur de multiples configurations 60 Hz, j’ai essayé), et le Le processeur est en fait en train de dormir la plupart du temps, les images sont très souvent ignorées. Selon la machine et l’utilisation du processeur pour des choses autres que le rendu, cela peut être aussi grave que de réduire de moitié la fréquence d’images. Ce problème ne semble pas être lié au pilote.

Comment faire fonctionner vsync sous Windows avec OpenGL en mode fenêtré, ou un effet similaire avec ces propriétés (si j’ai oublié quelque chose de remarquable, ou si quelque chose n’est pas raisonnable, veuillez commenter):

  • Le processeur peut dormir la plupart du temps
  • Aucune déchirure
  • Pas d’images sautées (en supposant que le système n’est pas surchargé)
  • Le CPU apprend quand une image a été affichée

DÉTAILS / QUELQUES RECHERCHES

Lorsque j’ai opengl vsync stutter ou opengl vsync frame drop ou des requêtes similaires, j’ai trouvé que beaucoup de gens ont ce problème (ou un problème très similaire), mais il ne semble pas y avoir de solution cohérente au problème réel (beaucoup la stack gamedev change également, ainsi que de nombreux messages de forum à faible effort.

Pour résumer mes recherches: Il semble que le gestionnaire de fenêtres de composition (DWM) utilisé dans les nouvelles versions de Windows force la mise en mémoire tampon sortingple, ce qui interfère avec vsync. Les gens suggèrent de désactiver DWM, de ne pas utiliser vsync ou de passer en plein écran, ce qui ne constitue pas une solution au problème initial (FOOTNOTE1). Je n’ai pas non plus trouvé d’explication détaillée expliquant pourquoi le sortingple tampon cause ce problème avec vsync ou pourquoi il est techniquement impossible de résoudre le problème.

Cependant, j’ai également testé que cela ne se produit pas sous Linux, même sur des PC très faibles. Par conséquent, il doit être techniquement possible (du moins en général) que l’accélération matérielle basée sur OpenGL permette l’activation de vsync sans ignorer les images.

En outre, ce n’est pas un problème lors de l’utilisation de D3D au lieu d’OpenGL sous Windows (avec vsync activé). Par conséquent, il doit être techniquement possible d’avoir un vsync sur Windows (j’ai essayé de nouveaux pilotes, d’anciens et de très anciens et différents matériels (anciens et nouveaux)), bien que toutes les configurations matérielles disponibles soient Intel + NVidia. t savoir ce qui se passe avec AMD / ATI).

Enfin, il doit y avoir un logiciel pour Windows, que ce soit des jeux, des applications multimédias, des productions créatives, des programmes de modélisation / rendu 3D utilisant OpenGL et fonctionnant correctement en mode fenêtré tout en conservant un rendu précis sans attendre le CPU. , et sans gouttes de cadre.


J’ai remarqué que lorsque vous avez une boucle de rendu traditionnelle comme

 while (true) { poll_all_events_in_event_queue(); process_things(); render(); } 

La quantité de travail que le processeur doit effectuer dans cette boucle affecte le comportement du bégaiement. Cependant, ce n’est certainement pas un problème de surcharge du processeur, car le problème se produit également dans l’un des programmes les plus simples que l’on puisse écrire (voir ci-dessous) et sur un système très puissant qui ne fait rien autre que d’effacer la fenêtre avec une couleur différente sur chaque image, puis de l’afficher).

J’ai également remarqué que cela ne semble jamais être pire que de sauter toutes les autres images (c.-à-d., Dans mes tests, le framerate visible était toujours entre 30 et 60 sur un système 60 Hz). Vous pouvez observer une violation du théorème d’échantillonnage de Nyquist lorsque vous exécutez un programme qui modifie la couleur d’arrière-plan entre 2 couleurs sur des images impaires et paires, ce qui me fait penser que quelque chose n’est pas correctement synchronisé (un bug logiciel dans Windows ou son implémentation OpenGL). Encore une fois, le framerate en ce qui concerne le processeur est solide. En outre, timeBeginPeriod n’a eu aucun effet notable dans mes tests.


(FOOTNOTE1) Il convient de noter cependant que, à cause du DWM, le déchirement ne se produit pas en mode fenêtré (ce qui est l’une des deux principales raisons d’utiliser vsync, l’autre raison étant de mettre le processeur en veille le plus longtemps possible) sans rater un cadre). Il serait donc acceptable pour moi d’avoir une solution qui implémente vsync dans la couche application.

Cependant, la seule façon de voir cela étant possible, c’est d’attendre explicitement (et avec précision) qu’un retournement de page se produise (avec possibilité de dépassement de délai ou d’annulation), ou d’interroger un indicateur non collant La page est retournée (d’une manière qui ne force pas à vider tout le pipeline de rendu asynchrone, comme le fait par exemple glGetError ), et je n’ai trouvé aucun moyen de le faire non plus.


Voici un code pour lancer un exemple rapide illustrant ce problème (en utilisant SFML, ce qui m’a semblé le moins pénible pour travailler).

Vous devriez voir un clignotement homogène. Si vous voyez la même couleur (noir ou violet) sur plus d’une image, c’est dommage.

(Cela clignote l’écran avec le taux de rafraîchissement de l’écran, alors peut-être l’avertissement d’éstackpsie):

 // g++ TEST_TEST_TEST.cpp -lsfml-system -lsfml-window -lsfml-graphics -lGL #include  #include  #include  #include  #include  int main() { // create the window sf::RenderWindow window(sf::VideoMode(800, 600), "OpenGL"); window.setVerticalSyncEnabled(true); // activate the window window.setActive(true); int frame_counter = 0; sf::RectangleShape rect; rect.setSize(sf::Vector2f(10, 10)); sf::Clock clock; while (true) { // handle events sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { return 0; } } ++frame_counter; if (frame_counter & 1) { glClearColor(0, 0, 0, 1); } else { glClearColor(60.0/255.0, 50.0/255.0, 75.0/255.0, 1); } // clear the buffers glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Enable this to display a column of rectangles on each frame // All colors (and positions) should pop up the same amount // This shows that apparently, 1 frame is skipped at most #if 0 int fc_mod = frame_counter % 8; int color_mod = fc_mod % 4; for (int i = 0; i  17 || elapsed_ms < 15) { // Ideally you should NEVER see this message, but it does tend to stutter a bit for a second or so upon program startup - doesn't matter as long as it stops eventually std::cout << elapsed_ms << std::endl; } // end the current frame (internally swaps the front and back buffers) window.display(); } return 0; } 

Information système:

Vérifié ce problème sur ces systèmes:

  • Windows 10 x64 i7-4790K + GeForce 970 (vérification que le problème ne se produit pas sous Linux ici) (moniteur 60 Hz unique)
  • Windows 7 x64 i5-2320 + GeForce 560 (moniteur 60 Hz)
  • Windows 10 x64 Intel Core2 Duo T6400 + GeForce 9600M GT (vérifié que le problème ne se produit pas sous Linux ici) (écran d’ordinateur portable unique de 60 Hz)
  • Et 2 autres personnes utilisant respectivement Windows 10 x64 et 7 x64, les deux «plates-formes de jeu costaudes», peuvent demander des spécifications si nécessaire

MISE À JOUR 20170815

Quelques tests supplémentaires que j’ai effectués:

J’ai essayé d’append explicit sleep (via la bibliothèque SFML, qui appelle simplement Sleep depuis l’API Windows tout en veillant à ce que timeBeginPeriod soit minimal).

Avec ma configuration 60 Hz, une trame devrait idéalement être 16 2/3 Hz. Selon les mesures de QueryPerformanceCounter , mon système est, la plupart du temps, très précis avec ces sumil.

L’ajout d’un sumil de 17 ms me permet de rendre le rendu plus lent que le taux de rafraîchissement. Lorsque je fais cela, certaines images sont affichées deux fois (cela est prévu), mais aucune image n’est supprimée, jamais. La même chose est vraie pour des couchages encore plus longs.

L’ajout d’une veille de 16 ms entraîne parfois l’affichage d’une image deux fois et parfois la suppression d’une image. Ceci est plausible à mon avis, considérant une combinaison plus ou moins aléatoire du résultat à 17 ms, et le résultat sans sumil du tout.

L’ajout d’un sumil de 15 ms se comporte de manière très similaire à l’absence de sumil du tout. C’est bon pour un court instant, puis à peu près chaque seconde image est supprimée. La même chose est vraie pour toutes les valeurs de 1 ms à 15 ms.

Cela renforçait ma théorie selon laquelle le problème pourrait ne pas être autre chose qu’un simple bug de concurrence dans la logique vsync de l’implémentation OpenGL ou du système d’exploitation.

J’ai aussi fait plus de tests sous Linux. Je n’avais pas vraiment beaucoup réfléchi à la question auparavant – j’ai simplement vérifié que le problème de suppression d’images n’existait pas et que le processeur dormait en fait la plupart du temps. J’ai réalisé que, en fonction de plusieurs facteurs, je peux faire en sorte que des déchirures se produisent systématiquement sur ma machine de test, en dépit de vsync. Pour l’instant, je ne sais pas si cette question est liée au problème d’origine, ou si c’est quelque chose de complètement différent.

Il semble que la meilleure approche serait de trouver des solutions de contournement et des hacks, et de totalement abandonner vsync et d’implémenter tout dans l’application (parce qu’en 2017, apparemment, nous ne pouvons pas obtenir le rendu de base le plus simple avec OpenGL).


MISE À JOUR 20170816

J’ai essayé de “désosser” un tas de moteurs 3D open source (en particulier avec obbg ( https://github.com/nothings/obbg )).

Tout d’abord, j’ai vérifié que le problème ne se produit pas là. Le taux de trame est lisse au beurre. Ensuite, j’ai ajouté mon bon vieux violet / noir clignotant aux couleurs vives et j’ai constaté que le bégaiement était en fait minime.

J’ai commencé à déchirer les entrailles du programme jusqu’à ce que je finisse avec un programme simple comme le mien. J’ai trouvé qu’il y a du code dans la boucle de rendu d’Obbg qui, une fois supprimé, provoque un bégaiement important (à savoir, rendre la partie principale du monde obbg ingame). En outre, il existe un code dans l’initialisation qui provoque également le bégaiement lorsqu’il est supprimé (à savoir, l’activation du multi-échantillonnage). Après quelques heures de bricolage, il semble que OpenGL a besoin d’une certaine charge de travail pour fonctionner correctement, mais je n’ai pas encore trouvé ce qui doit être fait. Peut-être que faire un million de sortingangles aléatoires ou quelque chose fera l’affaire.

J’ai également réaffirmé que tous mes tests existants se comportent légèrement différemment aujourd’hui. Il semble que j’ai globalement moins, mais les trames dissortingbuées de manière plus aléatoire tombent aujourd’hui que les jours précédents.

J’ai également créé un meilleur projet de démonstration qui utilise OpenGL plus directement, et depuis que obbg a utilisé SDL, je suis également passé à cela (bien que j’aie brièvement examiné les implémentations de la bibliothèque et cela me surprendrait s’il y avait une différence) est une surprise quand même). Je voulais aborder l’état “fonctionnel” à la fois du côté obbg-based, et du côté du projet vide afin que je puisse être vraiment sûr du problème. Je viens de mettre tous les binarys SDL requirejs dans le projet; Si vous disposez de Visual Studio 2017, il ne devrait y avoir aucune dépendance supplémentaire et cela devrait se produire immédiatement. Il y a beaucoup de #if s qui contrôlent ce qui est testé.

https://github.com/bplu4t2f/sdl_test

Au cours de la création de cette chose, j’ai également revu comment se comporte l’implémentation de D3D de SDL. Je l’avais déjà testé auparavant, mais peut-être pas assez. Il n’y avait toujours pas de trames en double et aucune image ne tombait du tout, ce qui est bien, mais dans ce programme de test, j’ai implémenté une horloge plus précise.

À ma grande surprise, je me suis rendu compte que lors de l’utilisation de D3D au lieu d’OpenGL, de nombreuses itérations de boucles (mais pas la majorité) se situaient entre 17,0 et 17,2 ms (je n’aurais pas compris cela avec mes programmes de test précédents). Cela ne se produit pas avec OpenGL. La boucle de rendu OpenGL est toujours dans la plage 15.0 .. 17.0. S’il est vrai qu’il faut parfois un délai d’attente légèrement plus long pour le blanc vertical (quelle que soit la raison), alors OpenGL semble le manquer. Cela pourrait être la cause profonde de la chose entière?

Encore un jour de regarder littéralement un écran d’ordinateur vacillant. Je dois dire que je ne m’attendais vraiment pas à passer autant de temps à rendre rien d’autre qu’un arrière-plan vacillant et je ne l’aime pas particulièrement.