Darstellungs-Lags bei Animation
lima-city → Forum → Programmiersprachen → Java
animation
bild
bildschirm
code
datum
fenster
funktion
import
konsole
mache
maus
problem
programm
sekunde
swing
system
url
vergangenen sekunden
versuchen
warte
-
Hallo zusammen,
ich versuche mit Java ein Spiel zu programmieren, das Problem ist aber, dass die Grafik zeitweise extrem ruckelt.
Hier ein minimales Beispiel, in dem der Fehler auftritt:package test; import java.awt.Graphics; import javax.swing.JFrame; import javax.swing.JPanel; public class MyPanel extends JPanel implements Runnable { static final long serialVersionUID = 1l; private int frameNr; public MyPanel() { new Thread(this).start(); } public void paintComponent(Graphics g) { super.paintComponent(g); frameNr++; g.drawString("Frame Nr. " + frameNr, 10, 50); System.out.println(frameNr); // wird regelmäßig und flüssig in die Konsole ausgegeben } public void run() { try { while (true) { Thread.sleep(20); this.repaint(); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { JFrame frame = new JFrame(); frame.setSize(400, 200); frame.add(new MyPanel()); frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
Die Gameloop ruft 50 Mal pro Sekunde repaint() auf. In der paintComponent() Methode zähle ich die tatsächlich gezeichneten Frames und schreibe die Nummer auf die Konsole und in das JPanel.
Die Ausgabe auf die Konsole erscheint immer regelmäßig und flüssig. (siehe Kommentar Zeile 20) Auf dem JPanel sehe ich jedoch zeitweise nur noch zwei Frames pro Sekunde. Der Zähler springt dort dann in 25er-Schritten nach oben.
Es scheint, als würde das Java-Programm die Frames regelmäßig zeichnen, aber sie werden eben nicht regelmäßig auf dem Bildschirm angezeigt.
Außerdem beobachte ich eine Abhängigkeit zu meinem Mauszeiger. Das Problem tritt überwiegend dann auf, wenn sich der Mauszeiger außerhalb des Fensters befindet oder wenn er im Fenster ruht. Wenn ich die Maus dagegen permanent innerhalb des Fensters bewege, bekomme ich eine flüssige Darstellung.
Ich nutze Ubuntu 16.04 mit der Unity-Oberfläche.
Als Hardware habe ich einen Intel Core i7 Prozessor und eine Intel Grafikkarte.
Kann mir jemand erklären, wie es zu dem Problem kommt und wie man es beheben kann? -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage
-
Ohne ein Java-Profi zu sein...
Es ist relativ undurchsichtig, was du da versuchst. Aber ich versuche mal einige Schlüsse zu ziehen.
Zum einen weisen Funktionen wie "Sleep" darauf hin, dass du die Funktion von Framerates nicht verstanden hast. Du wartest nicht im Frame, du nimmst die Sekunden, wartest bis diese auf 1 höher als vorher sind und inkrementierst bis dahin eine Frames-Variable. Wenn die vergangene Zeit größer als 1 Sekunde ist, teilst du die aktuellen Frames durch die vergangenen Sekunden und erhälst... Frames per Second. Dann setzt man die Frames und die vergangenen Sekunden auf 0. Und fährt fort. Da wartet man nicht.
Ähnliches gilt für Framerate-Begrenzer. Man wartet nicht, sondern zeichnet nur, wenn die aktuelle Zeit größer als die gewünschte Zeit ist. Du sagst nicht "mache alles, warte n sekunden, mach dann das nächste", weil du dann immer die FPS + "die Zeit die du gebraucht hast" bekommst. Du updatest deine Daten so oft wie möglich, aber zeichnest nur, wenn die gewünschte Zeit vergangen ist.
Auch, wenn das sehr vage war, hoffe ich, dass es ansatzweise helfen konnte. -
noxious schrieb:
Ich zeige die "Frames per Second" ja nicht direkt an, das würde mir nichts bringen weil das dann ja auf 50 steht, ich aber trotzdem nur 2 Frames jede Sekunde sehen kann.
Zum einen weisen Funktionen wie "Sleep" darauf hin, dass du die Funktion von Framerates nicht verstanden hast. Du wartest nicht im Frame, du nimmst die Sekunden, wartest bis diese auf 1 höher als vorher sind und inkrementierst bis dahin eine Frames-Variable. Wenn die vergangene Zeit größer als 1 Sekunde ist, teilst du die aktuellen Frames durch die vergangenen Sekunden und erhälst... Frames per Second. Dann setzt man die Frames und die vergangenen Sekunden auf 0. Und fährt fort. Da wartet man nicht.
Was ich hier habe ist ein Framezähler, der kontinuierlich von Frame zu Frame hochzählt. Der Sinn davon ist es, eine Animation zu haben, damit sich das Bild zwischen den Frames auch jedesmal ändert. Ich hätte auch ein Rechteck im Kreis bewegen können, da gäbe es die selben Symptome.
noxious schrieb:
Mir ist bewusst, dass ich im Beispielprogramm oben nicht ganz exakt 50 FPS habe, weil von Sleep zu Sleep immer ein paar Mikrosekunden vergehen. Das ist aber auch ein Minimalbeispiel. Im ursprünglichen Programm schaue ich permanent auf die Uhr und warte nur so lange, wie es noch bis zur nächsten Frame dauert. Damit bekomme ich dann exakt 50 FPS.
Ähnliches gilt für Framerate-Begrenzer. Man wartet nicht, sondern zeichnet nur, wenn die aktuelle Zeit größer als die gewünschte Zeit ist. Du sagst nicht "mache alles, warte n sekunden, mach dann das nächste", weil du dann immer die FPS + "die Zeit die du gebraucht hast" bekommst. Du updatest deine Daten so oft wie möglich, aber zeichnest nur, wenn die gewünschte Zeit vergangen ist.
Das Problem ist ja nur ein Anzeigeproblem. Es lässt sich innerhalb des Programms nicht messen, da die Gameloop und paintComponent() regelmäßig aufgerufen werden und nirgendwo hängen bleiben.
Ok, ich habe einmal testweise das
ersatzlos gestrichen. Nun lastet das Java-Programm 1,6 CPU-Kerne aus und es werden ca. 33000 Frames pro Sekunde gezeichnet. Tatsächlich sichtbar sind jetzt ca. 18 Frames jede Sekunde, sehe ich auf einer Zeitlupenaufnahme meines Bildschirms. Das ist besser als 2 aber immernoch nicht gut, denn meines Wissens nach kann mein Bildschirm 60 Bilder pro Sekunde darstellen.Thread.sleep(20)
Wenn ich jetzt den Framerate-Begrenzer einbaue, wie du ihn beschrieben hast, bin ich wieder da wo ich vorher war: Bei 50 gezeichneten Frames pro Sekunde, von denen 2 sichtbar sind. Und im Prinzip habe ich nur den Sleep-Befehl durch eine Busy-Waiting Warteschleife ersetzt, die jetzt einen CPU-Kern voll auslastet.
Es wäre interessant, wenn mal jemand ein sauberes Beispiel schreiben könnte, das irgendeine Animation zeigt ohne dass man Maus oder Tastatur benutzen muss. Das würde ich dann bei mir testen und berichten.
Edit: Jetzt habe ich das Problem endlich gelöst!
Ich habe am Ende der Methode paintComponent() folgende Zeile eingefügt:
Toolkit.getDefaultToolkit().sync();
In der Dokumentation steht dazu:Synchronizes this toolkit's graphics state. Some window systems may do buffering of graphics events.
Anscheinend ist die Ubuntu Unity-Oberfläche eines dieser Systeme, die die Grafikausgabe puffern. Deshalb brauche ich die Zeile um ein flüssiges Bild zu bekommen, während es auf anderen Betriebssystemen auch so schon flüssig läuft.
This method ensures that the display is up-to-date. It is useful for animation.
Beitrag zuletzt geändert: 5.8.2017 21:07:59 von fuerderer -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage