Laden...

GDI+: FPS und Zeit zeichnen - überfordert?

Erstellt von Jack_AI vor 16 Jahren Letzter Beitrag vor 16 Jahren 3.838 Views
J
Jack_AI Themenstarter:in
193 Beiträge seit 2007
vor 16 Jahren
GDI+: FPS und Zeit zeichnen - überfordert?

Hallo.

Ich versuche mit GDI+ die Bewegung eines Objektes zu realisieren. Dazu lasse ich, wie in diesem Thread geschildert, ein Objekt von einem Punkt zum anderen wandern. Zuerst wollte ich das so lösen, dass die Bilder nach einer vorgegebenen Zeit aktualisiert werden. Dazu habe ich anfangs mit 30 Aktualisierungen pro Sekunde gearbeitet (30 Bilder/Sekunde = 30 FPS).

Allerdings ist ein merkwürdiger Fehler aufgetreten: Das Objekt "springt" plötzlich, obwohl es sich geradeaus bewegen soll. Als ich die feste FPS-Rate auf 100 erhöht habe, trat dieser "Sprung" früher auf, als ich sie gesenkt habe, trat er früher oder gar nicht auf.

Auf dem Bildschirm wird zudem eine Zeitanzeige angezeigt, die auf die erste Nachkommastelle genau immer die Zeit seit Programmstart anzeigen soll. (Auf erste Nachkommastelle genau heißt also, dass sie zumindest alle 1/10 Sekunden aktualisiert werden soll). Aber schon bei 10 FPS tritt dieser Fehler auf.

Mein Verdacht ist also, dass durch die festgegebene Frequenz das Programm mit den vielen Berechnungen pro Sekunde nicht ganz fertig wird, und so die Berechnungen verfälscht werden (die Zeit fließt in die Berechnung mit ein). Ist dieser Verdacht berechtigt? Wenn ja, müsste ich es anders realisieren. Aber wie halte ich dann die Uhrzeit aktuell?

Danke im Voraus,
euer verzweifelter Jack

871 Beiträge seit 2005
vor 16 Jahren

Hallo Jack_AI,

also wenn ich eine Feste Framerate haben möchte mache ich es beispielsweise so:* Du weisst dass Du angenommen 25 Frames pro Sekunde haben möchtest. D.h. Du musst alle 40 ms einen Frame darstellen.

  • Bei der Methode die Du zum zeichnen verwendest lässt Du eine StopWatch mitlaufen - Vom anfang bis zum ende der Berechnung. 40ms abzüglich der vergangenen Zeit ergibt jene Zeit die deine Zeichenroutine noch warten muss (Thread.Sleep).

Obiges ist nur sehr vereinfacht dargestellt aber ich glaube es zeigt den Weg wo es hingehen soll.

Falls Du in einen Backbuffer zeichnest musst Du natürlich auch noch die Zeit miteinkalkulieren die benötigt wird um den veränderten Ausschnitt auf den Schirm zu zaubern (Invalidate)

Grüsse,
Egon

J
Jack_AI Themenstarter:in
193 Beiträge seit 2007
vor 16 Jahren

Das ist gar nicht mein Problem.

Ich stelle meine Frage noch einmal anders:
Was passiert denn, wenn das Programm für die Berechnungen länger braucht, als den Bruchteil einer Sekunde, den ich dem Programm zur Verfügung stelle? Ich sage dem Programm ja, "Hey, Programm! Berechne das Ganze in 40ms noch mal neu!" - "Und wenn ich das in der Zeit nicht schaffe, mein Gebieter?" - "Dann... dann..." Ja, was dann?

915 Beiträge seit 2006
vor 16 Jahren

Hrm,
ich würde das ganze erst einmal in einen Probeprojekt abhandeln indem ich erstmal keinerlei Berechnungen anstelle sondern nur ein Grafisches- Objekt gerade bewegen lasse indem x erhöhst. Anschließen würde ich die Formel nach für nach implementieren bis die Stelle gefunden hast in der mehr Zeit als 40ms verbrauchst.

Normal sollte die Berechnung von deinen vorangegangenen Thread nicht wirklich belastent sein. So das ich eher andere Fehlerquellen vermute - nur dazu brauche ich die Zeichenroutine von dir + Aufruf als Code zum sichten.

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

J
Jack_AI Themenstarter:in
193 Beiträge seit 2007
vor 16 Jahren

Normal sollte die Berechnung von deinen vorangegangenen Thread nicht wirklich belastent sein. So das ich eher andere Fehlerquellen vermute - nur dazu brauche ich die Zeichenroutine von dir + Aufruf als Code zum sichten.

Ich weiß nicht, ob dir das wirklich helfen wird. Ich poste das hier einfach mal. Hoffnung habe ich nicht wirklich.

Konstruktor:


    public Form1()
    {
      InitializeComponent();

      ...

      // EventHandler
      pn_Flaeche.Paint += new PaintEventHandler(pn_Flaeche_Paint);

      // Timer
      RefreshTimer = new Timer();
      RefreshTimer.Interval = Convert.ToInt32((1 / (double)ClassGlobal.FPS) * 1000); // FPS = 30
      RefreshTimer.Tick += new EventHandler(RefreshTimer_Tick);
    }

    // Timer-Ticks
    void RefreshTimer_Tick(object sender, EventArgs e)
    {
      ClassGlobal.Time++;
      pn_Flaeche.Invalidate(); 
    }

Zeichen-Routine:


    void pn_Flaeche_Paint(object sender, PaintEventArgs e)
    {      
      if (ClassGlobal.ProgramStarted)
      {
        if (Objektliste.Count > 0)
        {
          foreach (ClassObject Objekt in Objektliste)
          {
            // Hinweis: Problem tritt schon bei nur einem Objekt auf
            Objekt.DoYourJob(e.Graphics); // Objekt wird gezeichnet und Bewegung wird berechnet
          }
        }

        // Zeit anzeigen
        e.Graphics.DrawString(Math.Round(ClassGlobal.SITime(), 1).ToString(), new Font("Times New Roman", 12, FontStyle.Regular), 
          Brushes.Black, new PointF(ClassGlobal.Bildbreite - 50, ClassGlobal.Bildhoehe - 30));
      }
    }

Was mir aufgefallen ist: Wenn sich das Objekt nur auf der X- oder Y-Achse bewegt, tritt dieser Effekt nicht. Für "schräge" Bewegungen werden mehr Berechnungen gebraucht. Daher vermute ich, dass entweder die komplexeren Rechungen einen Fehler enthalten (was ich eigentlich ausschließe), oder diese Berechnungen zu viel Zeit brauchen.

871 Beiträge seit 2005
vor 16 Jahren

Hallo,

abgesehen davon dass ich die gesamte Zeichenroutine anders gestalten würde sehe ich kein gravierendes Problem in deinem Code (würde in einen Puffer zeichnen und für jeden Frame nur die Veränderten Ausschnitte zeichnen)

Um einmal auszuschliessen dass deine Berechnungsroutinen zu lange brauchen: Lasse bei jeder Berechnung eine StopWatch mitlaufen und schreib das ganze in ein File. Wenn Du einen Profiler hast dann benutz den mal.

Wenn das nicht das Problem sein sollte würde ich so vorgehen:

  • Einen Thread erstellen der sich in einer Schleife um das Update der Objektpositionen kümmert. Vor jedem Ende eines Schleifendurchgangs wird so lange gewartet bis insgesamt (1/FPS)*1000 ms vergangen ist (StopWatch; Thread.Sleep)
  • Am Ende der Schleife wird dann ein Invalidate auf die entsprechende Region gemacht. Während nun neu gezeichnet wird führt der Berechnungsthread schon wieder die nächste Berechnung durch.

(Obiges Beispiel sollte mit einem Backbuffer durchgeführt werden)

Grüsse,
Egon

915 Beiträge seit 2006
vor 16 Jahren

Hrm, okay ich denke an der stelle verlierst du keine Performance.

if (Objektliste.Count > 0) kannst dir spaaren da die foreach Schleife das schon übernimmt. Denk dran, je weniger Abfragen und Aufrufe desto mehr Performance bekommst du rein - Aber das ist nicht der Zeitkiller. Kannst du bitte vom Grafischen Object die Funktion DoYourJob() noch reinmachen - evtl ist dort ein Aufruf der zu viel Zeit kostet.

Ansonsten, egrath hat nicht unrecht aber eigentlich sollte das nicht nötig sein - zumindest nicht wenn mehrer tausende von Objecten darstellst.

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

J
Jack_AI Themenstarter:in
193 Beiträge seit 2007
vor 16 Jahren
noch ein Problem...

OK, ich habe festgestellt, dass das Problem an der Logik lag. Es hat mit dem Zeichnen an sich nichts zu tun.

Allerdings habe ich gerade ein andere Problem bemerkt:
Ich habe ja meine Endlosschleife, in der alle paar Millisekunden das Bild neu gezeichnet wird. Allerdings wird das Bild auch neu gezeichnet, wenn das Bild durch andere Umstände (z.B. wenn ich ein anderes Fenster darüber ziehe) neu gezeichnet werden muss. Dadurch werden wird die Darstellung beschleunigt. Wie kann ich das verhindern?

871 Beiträge seit 2005
vor 16 Jahren

Hallo,

indem Du periodisch nur in einen BackBuffer zeichnest und in der OnPaint nur mehr diesen auf den Screen kopierst.

Grüsse,
Egon