[C++]Initial-Daten für Ressourcen sauber rumreichen.

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
Benutzeravatar
Schrompf
Moderator
Beiträge: 4855
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

[C++]Initial-Daten für Ressourcen sauber rumreichen.

Beitrag von Schrompf »

Moin,

inspiriert von meinem Gemecker und der Meinung des Internets (tm) dazu möchte ich euch ein Problem vorstellen. In meinem neugeschriebenen Renderer gibt es ein Rudel Funktionen, die GPU-Ressourcen erzeugen. Z.b. ein ganz banalen VertexBuffer.

Den VertexBuffer kann man mit Initial-Füllung oder auch uninitialisiert erzeugen, wenn man ihn später beschreibt. Ich habe bei mir die Use Cases reduziert auf zwei Optionen: entweder initial befüllt und dann unveränderlich, oder nachträglich veränderlich und dann für häufigere Schreibzugriffe optimiert. Das aber nur am Rande. Die Daten für den Konstruktor und für die Schreib-Funktionen habe ich in eine kleine Klasse gekapselt, die außerdem die Lebenszeit verwaltet. Die sieht gekürzt so aus:

Code: Alles auswählen

/// Beschreibt, wie mit einem übergebenen Datensatz zu verfahren ist.
enum class TDatenNutzung
{
  /// Verwiesene Daten sind bald wieder weg, müssen also kopiert werden, wenn sie nach Ende der Funktion noch gebraucht werden
  MussKopieren,
  /// Verwiesene Daten sollen in den Besitz des Aufgerufenen übergeben werden und müssen von ihm gelöscht werden, wenn fertig
  MussUebernehmen,
  /// Verwiesene Daten bleiben längerfristig verfügbar und können auch später gelesen werden, ohne sie zu duplizieren.
  DarfVerweisen
};

/// Beschreibt einen Satz Daten, mit dem eine Ressource initialisiert werden kann, plus die Verwendungsmethode.
template <typename Datum> 
struct TInitDaten
{
  Datum mDaten;
  TDatenNutzung mNutzung;
};
Damit kann ich jetzt jeder Ressourcen-Funktion die Daten mitgeben und zusätzlich eine Vorschrift, wie damit zu verfahren ist. Standard ist MussKopieren, um auf der sicheren Seite zu sein. Tatsächlich kopiert wird dann aber nur, wenn die Daten wirklich länger als bis zum Ende der aufgerufenen Funktion benötigt werden, z.B. beim indirekten Rendern mit einer Command Queue aus einem parallelen Thread. Wenn man dann den Inhalt eines VertexBuffers aktualisieren will, müssen die Daten so lange leben, bis sie tatsächlich an DirectX oder OpenGL übergeben werden können.

Eigentlich kam ich mir ziemlich clever vor, als ich mir das System ausgedacht hatte. Man kann jetzt irgendwelche Daten entweder referenzieren oder in den Besitz der Ressource übergeben, und die Klasse selbst hat ein paar schlichte Funktionen, um Besitz sicherzustellen oder die Daten zu entsorgen, je nachdem was die DatenNutzung vorgibt. Das vereint sich außerdem prima mit dem Direct3D9-Renderer, für den DeviceLoss und damit Verlust aller VideoRAM-Ressourcen ja noch eine reale Bedrohung ist. Dafür hält der DX9-Renderer, wenn gewünscht, noch eine Hauptspeicherkopie vor, die nach bester std::move()-Gepflogenheit auch direkt übernommen werden kann, wenn die Quelldaten eh verzichtbar sind.

Die Frage ist jetzt: kann man das irgendwie clever RAII-Mit-C++11-Move-mäßig lösen? Gibt es irgendwelche Dummheiten, die ich übersehen habe?

Ausgangspunkt ist mein Ärger über GCC, wie im Jammer-Thread beschrieben. Alle Ressourcen-Funktionen nehmen halt als Default-Parameter ein temporäres invalides Daten-Objekt für den Fall, dass die Ressource ohne Initialdaten-Befüllung erstellt werden soll. Und das wird dann noch über ein paar Zwischenfunktionen weitergereicht. All diese Zwischenfunktionen nehmen also eine nicht-konstante Referenz auf so ein Datenobjekt, weil sie die übergebenen Daten dann ja u.U. löschen müssen. Also z.B. so:

Code: Alles auswählen

Traum_VertexBuffer* ErzeugeVertexBuffer( size_t pGroesse, size_t pFlags = 0, TInitDaten<TDatenBlock>& pDaten = TInitDaten<TDatenBlock>());
Visual Studio hat damit kein Problem. GCC steigt mir nun auf's Dach, weil es kein temporäres Objekt an eine nichtkonstante Referenz binden will. Mistgriebel. Aber bevor ich alle Funktionen verdoppel, dachte ich mir, dass ich euch erstmal frage, ob es ne kluge Lösung dafür gibt.

Kurzzusammenfassung:
TInitDaten<> ist ein Verweis auf einen Datenblock mit Angabe der "Haltbarkeit". Mögliche Verwendungszwecke sind:
a) Daten müssen kopiert werden, wenn man sie länger braucht
b) Daten müssen übernommen werden, wechseln also den Besitzer.
c) Daten dürfen schwach referenziert werden, weil der Aufrufer garantiert, dass sie da bleiben.

Im Falle a) werden die Daten kopiert, wenn sie länger gebraucht werden, also z.B. für DX9-Hauptspeicherkopie oder bei verzögertem Schreiben aus parallelem Thread.
Im Falle b) werden die Daten übernommen und vom Aufgerufenen am Ende entsorgt, müssen dann aber nicht kopiert werden.
Im Falle c) ist eh alles smooth, aber der Aufrufer muss garantieren, dass die Daten länger leben als jeder Verweis. Wird aktuell für Texturatlanten verwendet, weil die für pixelgenaue Kollision auch Lesezugriffe auf die Grafikdaten erlauben.

Im Falle a) sind die verwiesenen Daten konstant, aber u.U. nicht der Verweis selbst.
Im Falle b) ist der Verweis konstant, die verwiesenen Daten aber für Veränderungen freigegeben.
Im Falle c) sind sowohl der Verweis als auch die verwiesenen Daten konstant.

Ideen?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: [C++]Initial-Daten für Ressourcen sauber rumreichen.

Beitrag von Spiele Programmierer »

Warum übergibst du nicht einfach einen Pointer auf die Daten und die Besitzangabe als zusätzlichen Parameter?
Ich verstehe gerade nicht ganz, warum du dafür überhaupt diese Template Klasse brauchst.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4855
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++]Initial-Daten für Ressourcen sauber rumreichen.

Beitrag von Schrompf »

Das ist nur ein Helfer. Das System arbeitet auch für Texturen und CubeMaps, also sind es bisweilen recht komplexe Datenstrukturen, die da verwaltet werden. Für die Problemstellung ist das wurscht, aber ich hätte es wohl dazusagen sollen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Ingrater
Establishment
Beiträge: 103
Registriert: 18.04.2007, 21:52

Re: [C++]Initial-Daten für Ressourcen sauber rumreichen.

Beitrag von Ingrater »

Code: Alles auswählen

template <typename T>
TInitDaten<T>& InvalidTInitDaten()
{
  static TInitDaten<T> invalidInstance;
  return invalidInstance;
}

Traum_VertexBuffer* ErzeugeVertexBuffer( size_t pGroesse, size_t pFlags = 0, TInitDaten<TDatenBlock>& pDaten = InvalidTInitDaten<TDatenBlock>());
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: [C++]Initial-Daten für Ressourcen sauber rumreichen.

Beitrag von Spiele Programmierer »

Um ehrlich zu sein, bin ich etwas verwirrt, wofür du das ganze brauchst.
Um eine allgemeine Aussage zu treffen: Bei Parametern die nicht immer gebraucht werden, würde ich die Daten als Pointer und nicht als Referenz übergeben. Wenn der Parameter nicht benötigt wird, kannst du einfach 0 übergeben. Wenn du eine Optional-Struktur hast, wäre das auch eine Option, aber hier nicht unbedingt notwendig. Lvalue Referenzen die nicht konstant sind, würde ich ohnehin vermeiden, weil sie häufig sehr schwer ersichtliche Seiteneffekte aufweisen.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4855
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++]Initial-Daten für Ressourcen sauber rumreichen.

Beitrag von Schrompf »

Gute Idee. Einfach eine Funktion nehmen, die mir einen Standardwert erzeugt, anstatt den Standardwert direkt zu erzeugen. Minimalinvasiv, gefällt mir. Mir ist zwischenzeitlich aber auch gedämmert, dass ich auch mit const Referenzen arbeiten könnte, wenn ich "Daten konstant" und "Verweis konstant" ordentlich trenne. Also z.B. den Verweis immer konstant machen und für den Fall, dass ich Kopien davon anlege, halt eine neue Verweis-Instanz rausgebe.

@Spieleprogrammierer: brauchen tu ich das, weil ich allen möglichen Funktionen Daten mitgeben will und ihnen gleichzeitig mitteilen, was sie mit den Daten machen dürfen oder müssen. Weil Teile davon halt in anderen Threads laufen könnten, oder weil Teile davon in den Besitz des Aufgerufenen übergehen müssen. Aber egal, ich merk schon, das Thema hat zuviele Ecken, um sie in einer Forendiskussion sinnvoll darstellen zu können.

Ich hatte alternativ überlegt, Besitzerwechsel einfach über RValue-Refs zu implementieren. Dazu müsste ich dann aber alle Funktionen und Konstruktoren wieder doppelt schreiben - einmal für const Ref& und einmal für Ref&&. Immerhin wäre dann idiomatisch klar, wie sich die Verwendung unterscheidet. Dafür müsste die Ref dann notfalls Daten kopieren, falls der Nutzer mal ein std::move vergisst, und keiner würde es merken, weil einfach stillschweigend die andere Überladung aufgerufen wird.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Antworten