[GDI+/GDI/Sonstiges] VSync nachrüsten mit D2D

  • Moin,

    Wir wir alle wissen funktioniert der VSync mit D2D wunderbar. Ich hatte schon 50x vor die 3 dafür nötigen Schritte zusammenzufassen, damit man in GDI+/GDI/usw Skripten den VSync einfach nachrüsten kann.

    Wie funktioniert es ?
    - Die UDF ermöglicht es auf den VSync zu warten, dabei wird das Skript auf 60 FPS beschränkt.
    - Hat man gewartet, so kann man DANACH die eigentliche Zeichenoperation ausführen. Man hat dann ca. 16 Millisekunden Zeit das neue Bild ins Fenster zu schaffen, wenn man diese Zeit nicht einhalten kann (z.B. wenn man GDI+ nutzt und das Fenster zu groß ist), so tritt selbstverständlich trotzdem Tearing auf. Sinn macht diese UDF also nur, sofern der eigentliche Zeichenvorgang schneller als die 16ms läuft (BitBlt ist da so ein Kandidat der das kann) und das Skript (wenn man jedes Sleep ausbaut) wesentlich schneller als mit 60 FPS läuft.
    - Es wird an keiner Stelle D2D zum Zeichnen benutzt, sondern nur zum Warten. Dadurch kann man die UDF in jedes beliebige Skript integrieren.

    Edit: Als Beispiele sind 2 Skripte (eins mit GDI, eins mit GDI+) beigelegt. Das GDI+ Beispiel wird auf einigen Rechnern trotzdem Tearing zeigen, da das Zeichnen für die gewählte Fenstergröße ggf zu lange dauert.

    Anregungen, Verbesserungen, Gefundene Fehler, usw sind gerne gesehen.

    Edit1: Eine verbesserte Version von eukalyptus mit Errorhandling gibt es in Post 5.

    lg
    M

  • Hi,
    in der vorliegenden Form erreichen beide Scripte auf meinem Laptop 60fps.

    Vergrößere ich das Fenster im GDI+-Script auf das 1,5-fache, bricht die Framerate auf knapp die Hälfte zusammen, anders gesagt benötigt _GDIPlus_GraphicsDrawImage() statt 10ms nun 25...

    Im GDI-Script kann ich aber machen was ich will, Fenstergröße und auch das Quadrat bis aufs 3-fache vergrößern ändert nichts an der konstanten Framerate von 60.

    Ich würde wirklich gerne mal den GDI+Code sehen, ab und zu bin ich schon mit dem Debugger durch den Code einiger GDI+-Funktionen gepflügt, aber nach 1/2h rumgespringe durch den Speicher ohne wirkliches Ergebnis verlässt einen dann doch die Lust ||

  • 10*1,5 = 25, wann wurde das neu festgelegt


    Richtig ist (ungefähr), dass 10*1,5*1,5 = 25, vergrößert man B und H um je 1,5, vergrößert sich die Fläche auf Faktor 2,25 :D

    Wenn ich jetzt mal klugscheißern wollte würde ich feststellen, dass im ursprünglichen Script die Framerate auf 60FPS limitiert ist! Ohne diese Limitierung sind es ca. 80 FPS, einfach herauszufinden indem man _VSync_Wait() auskommentiert.
    Wird jetzt das Fenster vergrößert ($iH und $iW = 512*1.5 ), verändert sich die Framerate von ca. 80 auf die von mir festgestellten 33-35FPS. Das wiederum ist ausschließlich auf die Laufzeit von _GDIPlus_GraphicsDrawImage() zurückzuführen (profiling FTW)!

    Beim "Blitten" erreiche ich ohne Limitierung 750-760 FPS, bei Vergrößerung des Fensters auf B*2.5 und H*1.5 (Fläche vergrößert auf das 3.75-fache) werden 215 FPS erreicht, also genau die gleiche lineare Abhängigkeit der Laufzeit der Funktion von der Fenstergröße.

    Deshalb sollte man sich bei der Programmierung im Hinblick auf Geschwindigkeit auch Gedanken machen, ob der gesamte Fensterinhalt "erneuert" werden sollte! Wenn ein (oder mehrere) "Sprites" im Fenster vor konstantem Hintergrund bewegt werden sollen, dann ist es wesentlich sinnvoller, statt eines "Backbuffers" des gesamten Fensterinhalts nur den aktuellen "Hintergrund" des Sprites (plus nächste Bewegungsänderung) zu sichern und diesen dann nach der Bewegung des Sprites wieder zu restaurieren. Mal angenommen, die Fenstergröße ist 300x300 und das einzelne Sprite 30x30 groß, dann ist der Geschwindigkeitsgewinn nur durch das "teilweise" Blitten ca. Faktor 100 (naja, zumindes irgendwas größer als 80 8) ) !
    Natürlich ist die Verwaltung bei mehreren Sprites aufwendig und kostet entsprechend Laufzeit, daher ist irgendwann der Break-Even erreicht und der Overhead der Sprite-Verwaltung frisst den Zeitgewinn auf....Hier das Optimum zu finden ist der Job des Programmierers!


    Anbei ein uraltes Script von mir, eine Demo was per "Blitten" so geht^^
    Spirale-Donut-Mona-Lisa.zip

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    2 Mal editiert, zuletzt von Andy (21. Mai 2016 um 11:56)

  • Sehr interessant!

    Alternativ hätte ich diese Version, welche nur mehr eine globale Variable verwendet und etwas schlanker ist:


    Um ganz auf globale Variablen zu verzichten, könnte man $__t_VSYNC auch als Return bzw. Byref übergeben...

  • Irgendwie gefällt mir das VSync von GDI+ nicht richtig, wenn ich Beispiele z.B. in JS mir ansehe, wo die Animationen echt butterweich abgespielt werden.

    Irgendwie ist immer ein leichtes zucken drinne...

    Beispiel:

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

  • eukalyptus: Vielen Dank für die verbesserte Version (die sogar Errorhandling enhthält :D).

    UEZ: Auf meinem Zweitrechner (Intel Q6600, GeForce GT630, Win8.1 x64) läuft jede Animation (meine 2 Beispiele und deins) ohne Einwände. Was ich aber bemerkt habe ist, dass es manchmal doch Probleme gibt, sofern man mehr als einen Bildschirm angeschlossen hat. Auf meinem Hauptrechner (i5 3570k, GTX260.216, Win8.1 x64) der wesentlich mehr Leistung hat ruckelt z.B. das GDI+ Beispiel von mir, deins konnte ich noch nicht testen, das hole ich aber nach sobald ich zu Hause bin.

    Edit1: UEZ: In deinem Beispiel hast zu das Warten auf den VSync nach deinem Zeichenvorgang. Das Funktioniert mit BitBlt und einem nicht voll ausgelasteten Skript wahrscheinlich in den meisten Fällen. Sicherheitshalber ist die umgekehrte Reihenfolge aber besser (also vor den BitBlt in z112).

    Edit2: wobei ich da auch etwas verwirrt bin... auf meinem kleinen Rechner funktionieren alle Beispiele absolut flüssig, egal ob ich das Warten vor- oder nachschalte. Auf meinem großen Rechner hat das aber einen Effekt...

    Edit3: Der Test ergab folgendes:
    2 Bildschirme: (Schneller Computer)
    - GDI Skript (Rechteck), ruckelt ein wenig (zyklisch), kein Tearing (ist bei BitBlt auch ungewöhnlich), Last 1%
    - GDI+ Skript (Kreis), ruckelt nicht, ab und zu Tearing (DrawImage braucht einfach manchmal 20ms+ da kann man nix machen), Last 4,5%
    - GDI/GDI+ von UEZ, ruckelt ein wenig (zyklisch), kein Tearing, Last 5,5%
    1 Bildschirm: (Schneller Computer)
    - GDI Skript (Rechteck), ruckelt ein wenig (zufällig), kein Tearing
    - GDI+ Skript, ruckelt nicht, kein Tearing (scheint flotter zu gehen, wenn nur 1 Bildschirm läuft ? seltsam...)
    - GDI/GDI+ von UEZ, ruckelt ein wenig (zufällig), kein Tearing
    1 Bildschirm: (Langsamer Computer)
    - Alle Skripte, ruckelt nicht, kein Tearing. (das verstehe ich nicht, aber okay...)

    Ich weiß nicht genau wie D2D beim VSync arbeitet. Entweder wird das Bild unmittelbar VOR dem Takt übermittelt (Grafikkartenintern geht das ja sehr schnell), und das Skript läuft weiter, obwohl der Takt erst in Bälde kommt, oder es wird auf den Takt gewartet und unmittelbar DANACH gezeichnet. Jedenfalls konnte ich das zufällige Ruckeln (1 Bildschirm) reduzieren, indem ich eine 0 bis 9999er For-Schleife nach _VSync_Wait() gepackt habe und anschließend den BitBlt durchgeführt habe. Ob das auf jedem Rechner klappt weiß ich nicht. Falls dem so ist müsste man nach dem Warten noch einen möglichst konstanten kurzen Recheneinschub einbauen und dann erst BitBlt benutzen.

    UEZ: Ersetze bitte mal Folgendes in deinem Skript und schau ob es dann immernoch ruckelt:

    Die GraphicsDrawStringEx Funktion ist immer im Bereich von 0.2ms bis 0.3ms und stellt damit einen geeigneten Abstandhalter dar.

    lg
    M