std::clock() vs m_bChanged-Flag

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

std::clock() vs m_bChanged-Flag

Beitrag von B.G.Michi »

Guten Tag zusammen

ich habe da eine Frage und weiss leider nicht so recht womit ich Google füttern soll.

Folgende Annahmen: es gibt Objekte mit jeweils einer Transformationsmatrix (Objectspace -> Worldspace). Weiter gibt es eine Kamera mit einer ViewProjectionmatrix (Worldspace -> Screenspace). Soweit so klar. Wird das Objekt bewegt, bekommt es einen Flag m_bChanged = true. Wird die Kamera bewegt setze ich bis jetzt ebenfalls einen Flag m_bChanged = true. Beim rendern überprüfe ich ob einer der beiden Flags gesetzt ist und berechne die WorldViewProjectionMatrix und lösche den Flag des Objekts. Am Ende des Frames wird der Flag der Kamera gelöscht. Wird nun ein Objekt allerdings ein Frame lang nicht gerendert in dem die Kamera bewegt bekommt das Objekt die Bewegung der Kamera nicht mit.

Nun meine Idee: ich setze für die Kamera kein Flag sondern speichere die "Zeitder letzten Änderung", z.B. mit std::clock() oder etwas ähnlichem (das nicht nach 36 Minuten resettet) und kann dann beim rendern eines Objektes einfach überprüfen ob
if(Objekt.m_bChanged oder Objekt."Zeit der Letzten registrierten Kameraänderung" < bzw. != Kamera."Zeit der letzen Änderung")
und setze dann
Objekt."Zeit der Letzten registrierten Kameraänderung" = Kamera."Zeit der letzten Änderung".

Sollte eigentlich recht performant sein: einmal std::clock() pro Kameraänderung, sprich normalerweise einmal pro Frame, und beim rendern nur ein "<" bzw. "!=". Aber mir kommt das irgendwie etwas quick and dirty vor. Es geht mir vor allem um das speichern des Zeitpunktes. Also meine Frage: ist das good practice?

Danke und viele Grüße
JFF_B.G.Michi
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: std::clock() vs m_bChanged-Flag

Beitrag von xq »

An sich finde ich den Gedanken gar nicht mal SO schlecht. Aber warum std::clock() nehmen, wenn man auch einfach einen uint64 mit der anzahl der frames nehmen kann? Damit dürftest du immer noch ca. 10 Mrd. Jahre Laufzeit bei 60 FPS haben und brauchst keine calls auf die systemuhr sondern nur ein einfaches ++.
Da es dir hier um Performance geht sollte das ja dann die bessere Lösung sein.
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: std::clock() vs m_bChanged-Flag

Beitrag von Spiele Programmierer »

Vor allen Dingen ist "std::clock" unter Umständen sehr ungenau.
Microsoft selbst, spricht von einer Genauigkeit dieser Funktionen von 10 bis 16 Millisekunden bei ähnlichen Funktionen, habe jedoch auch schon von niedrigeren Werten gelesen. Außerdem ist die Genauigkeit variabel.

Ich würde den Vorschlag von MasterQ32 auf jeden Fall unterstützen.
Möglicherweise reicht aber auch ein Int32.
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: std::clock() vs m_bChanged-Flag

Beitrag von B.G.Michi »

@MasterQ32: weil sich eine Kamera theoretisch auch innerhalb eines Frames ändern könnte. Aber die Idee mit dem ++ finde ich sehr nice, nur würde ich dann wohl einfach die Kameraänderungen als globale Variable durchzählen und nicht die Frames. Dürfte wohl die schnellste und einfachste Methode sein. Mach ich mir hald die Hände etwas "dirty" :D
Benutzeravatar
Jonathan
Establishment
Beiträge: 2371
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: std::clock() vs m_bChanged-Flag

Beitrag von Jonathan »

Ich glaube, ich würde es ganz sein lassen. Wenn ich das richtig sehe, ist deine Idee, das neuberechnen der Matrix zu sparen, wenn es nichts neu zu berechnen gibt. Aber letztendlich sind das 1-2 Matrixmultiplikationen, was wirklich nicht viel ist. Ich würde sogar behaupten, ein Systemaufruf um die aktuelle Zeit zu bekommen ist um Größenordnungen langsamer. Damit hättest du dann also ein "optimiertes" Programm, das sowohl komplexer, als auch langsamer ist - schlecht.

Die Idee mit dem Frame-Counter wäre da dann natürlich wesentlich schneller, als die tatsächliche Zeit abzufragen. Allerdings hast du damit immer noch einiges an Overhead für JEDES Objekt, egal ob es gezeichnet wird oder nicht. Tatsächlich könnte schon ein einziger bedingter Sprung (durch die nötige Abfrage, ob du etwas machen musst), langsamer sein, als es einfach IMMER zu tun. Wenn so eine Matrizenmultiplikation richtig implementiert ist, kann die CPU davon sehr sehr viele ausführen, in Normalfall ist das, was die CPU killt das wilde hin und herspringen, und mal hier und dann wieder da etwas rechnen. Wenn sie einfach nur Daten hat, die sie gradlinig durchrechnen kann, greifen alle Caches und andere Dinge optimal und du merkst so eine einzelne Multiplikation überhaupt nicht mehr.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8245
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: std::clock() vs m_bChanged-Flag

Beitrag von Krishty »

Aaaaaaaaaaaaalso … mit FPS-Zähler ist es deutlich schneller, aber es ist immernoch Murks.

Das ist so ein typisches Anti-Pattern: Man iteriert über alle Objekte und markiert ein paar. Danach iteriert man wieder; nur um sich zu erinnern, welche man markiert hat. Bäh.

Wenn sich die Kameraposition ändert, musst du *alle* Matrizen neu berechnen. Punkt. Dann implementier das auch so.

Für alle anderen Objekte: std::vector<std::pair<Matrix *, Matrix *>>, wo alle Matrizen eingetragen werden, die sich geändert haben; und die Zielmatrizen, in die die Ergebnisse geschrieben werden sollen. Vor dem Zeichnen eines Frames iterierst du die Liste; berechnest die Matrizen darin neu; und schmeißt sie dann weg.

Und bevor du Angst wegen den dynamischen Allokationen hast: Die geschehen drei, vier Mal am Anfang und danach hat die Liste ausreichend Kapazität für immer. Und du kannst es immernoch seeehr einfach optimieren, falls das nicht genug sein sollte.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4858
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: std::clock() vs m_bChanged-Flag

Beitrag von Schrompf »

Ich wollte ursprünglich auch einen Moving Flag vorschlagen, wie ihr es schon benannt habt. Anstatt einen bool isChanged zu nehmen, den man separat setzen muss, erhöht man einfach einen size_t, wodurch alle bisherigen "Flags" automatisch merken, dass sie veraltet sind.

Aber: ich habe das vor vielen Jahren sehr gern in der Splitterwelten-Engine benutzt, und inzwischen würde ich das gern alles wieder ausbauen. Man muss sehr vorsichtig sein mit solchen Lazy Update Schemes, man holt sich damit Probleme in den Code. Es fängt an mit einfachen const correctness-Konflikten (was ich immer für einen Hinweis auf Design-Fehler halte) bis zu Konflikten, wenn man den Code dann mal parallelisieren will. Ein gut definierter und abgegrenzter Update-Prozess ist einiges wert.

Nebenbei: die Berechnung zweier Matrizen ist echt kein Lazy Update wert. Die World Matrix wäre evtl. noch ne Überlegung wert, weil man zu deren Berechnung potentiell die ganze Parent-Kette abklappern muss, was miserabel cache-lokal ist. Aber es ist notwendig, und wie das schlaue Video von Herb Sutter drüben schon sagte: der wenigste Code ist performance-kritisch, auch wenn man zumeist den gegenteiligen Eindruck hat. Aber spätestens die Berechnung der View Matrix - die arbeitet nur auf cache-lokalen Daten und besteht vielleicht aus ein paar Dutzend Fließkomma-Ops, die der Compiler für Dich wahrscheinlich auch noch vektorisieren wird. Und es tritt vielleicht eins zwei mal pro Frame auf... Ich finde, das ist ein ganz schlechtes Thema, um daran Optimierungen zu diskutieren.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: std::clock() vs m_bChanged-Flag

Beitrag von B.G.Michi »

Also da es sich hier ja schon um eine gewisse Grundsatzfrage handelt möchte ich das etwas genauer beleuchten. Das ist auch noch nicht wahnsinnig performance kritisch aber ich hätte es gerne "schön" :)

Es ging mir dabei auch weniger um die eine Matrixmultiplikation als um das Update des Constant-Buffers. In diesem befinden sich neben der WorldViewProjection-Matrix (potentiell) auch noch andere Variablen und somit muss der ganze Buffer erst mal zur GPU geschrieben werden. Und dann ist der Cache vermutlich durch. Das ist etwas, das nur machen möchte wenn es auch nötig ist. Liege ich da denn richtig an dieser Stelle zu sparen?

@Krishty: wenn sich die Kamera bewegt müssen doch keinesfalls *alle* Matrizen neu berechnet werden, sondern nur für die Objekte, die gerade sichtbar sind und das ist (potentiell) ein sehr kleiner Teil der Scene.

Im Moment wird der Transformations-Constant-Buffer erst direkt vor dem Draw() irgendwo tief in SceneGraph::Render() geupdatet. Eben dann wenn klar ist, dass das Objekt gerendert wird und ich den Buffer auch wirklich benötige. Wenn ich das nun schon im Voraus machen würde, also irgendwo bei der Berechnung der World-Matrix, so müsste ich entweder den *kompletten* SceneGraphen bei jeder Kamerabewegung neu berechnen oder mir wieder merken, ob geupdatet werden muss, sprich wäre auch wieder bei einem Moving Flag. Das wäre zwar vielleicht eine Überlegung Wert, aber die WorldViewProjection-Matrix bleibt in jedem Fall abhängig von zwei komplett getrennten Entitäten (Objekt und Kamera).

Edit: Die Constant-Buffer sind nebenbei die einzigen Lazy-Updates, die beim rendern noch übrig sind. Bei allen anderen nur der Write zur GPU und bei der Transformation zusätzlich noch die Berechnung der WorldViewProjection-Matrix.
Antworten