Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

21

14.10.2010, 18:00

1. Ich presse doch jetzt garnichts mehr in eine globale variable. Durch ein Prototype* Cube::cubePrototype = NULL; in der cube.cpp und dem deklarieren als private kann man sonst nirgends mehr auf die variable zugreifen, weder lesend, noch schreibend, nur innerhalb der klasse, was ja genau der gewünschte effekt ist.

Gut damit ist aber nur der Scope geregelt. Deine statische Variable hat immer noch nur den einzigen Zweck als Zugriffspunkt zu einem Objekt zu fungieren dessen Lebensdauer nicht statisch ist. Abgesehen davon musst du doch selber das Gefühl haben dass dieses if(!staticVar) im Konstruktor grottenhässlich ist!?

2. RenderObject ist wie gesagt abstrakt, ich kann also kein RenderObject-objekt erzeugen. Ich verwende es eigentlich nur, um gemeinsam genutzte hilfsfunktionen an alle Kindklassen vererben zu können und Polymorphie nutzen zu können. Damit ich also eine RenderObject-std::list haben kann, und es mir egal sein kann, ob es jetzt Cubes oder Spheres sind. Deine Lösung würde so also nicht wirklich funktionieren.

Eben. Cube benutzt dann intern eine von RenderObject abgeleitete CubeInstance Klasse die das entsprechende Interface implementiert. Cube::CreateInstance() returned dann ein new CubeInstance(stuff). Polymorphie eben. Dass das so funktioniert kann ich dir versichern da ich das selber schon oft genug unter noch viel komplexeren Umständen (interne Daten sollen nicht unnötig redundant vorhanden sein aber alles muss gleichzeitig auf mehreren Grafikkarten verfügbar sein) erfolgreich so gemacht habe.

[...] aber meiner meinung nach recht sauber.

Findest du wirklich dass das eine saubere Lösung ist? Ich finde absolut nicht. Vielleicht ein paar Dinge um dich zum Nachdenken anzuregen:
Kann/Darf es passieren dass mehrere Threads gleichzeitig ein Cube Objekt erzeugen?
Ist es möglich dass jemand versucht einen Würfel zu erzeugen bevor die statische Variable einen korrekten Wert bekommen kann?
Was wenn du mehr als einen OpenGL Context brauchst (z.B. weil du mit mehr als einem Fenster arbeiten willst)?

n0_0ne

1x Contest-Sieger

  • »n0_0ne« ist der Autor dieses Themas
  • Private Nachricht senden

22

14.10.2010, 18:30

Vorneweg: das zitieren in diesem board ist wirklich extrem gewöhnungsbedürftig......
Kann/Darf es passieren dass mehrere Threads gleichzeitig ein Cube Objekt erzeugen?
Ist es möglich dass jemand versucht einen Würfel zu erzeugen bevor die statische Variable einen korrekten Wert bekommen kann?
Was wenn du mehr als einen OpenGL Context brauchst (z.B. weil du mit mehr als einem Fenster arbeiten willst)?
Zu 1: In meinem programm eigentlich nicht, aber selbst wenn, kann man das ganze doch auch thread-sicher gestalten
Zu 2: Du meinst die zählervariable? Dann nicht, da sie ja noch vor main() auf 0 gesetzt werden würde ^^
Zu 3: Hier erkenn ich nicht wirklich den zusammenhang
Eben. Cube benutzt dann intern eine von RenderObject abgeleitete CubeInstance Klasse die das entsprechende Interface implementiert. Cube::CreateInstance() returned dann ein new CubeInstance(stuff). Polymorphie eben. Dass das so funktioniert kann ich dir versichern da ich das selber schon oft genug unter noch viel komplexeren Umständen (interne Daten sollen nicht unnötig redundant vorhanden sein aber alles muss gleichzeitig auf mehreren Grafikkarten verfügbar sein) erfolgreich so gemacht habe.
OK, so könnte man es machen, die neuen klassen wie CubeInstance hättest du erwähnen müssen ^^ Nur noch eine Frage: gibt es in deinem szenario jetzt nur 1 cube-objekt? Weil wie würdest du jetzt sicherstellen, dass alle Cube-Objekte den von ihren kreierten CubeInstances den selben pointer auf den Prototyp mitgeben? Meiner meinung nach sind wir dann wieder bei der ausgangssituation.
Gut damit ist aber nur der Scope geregelt. Deine statische Variable hat immer noch nur den einzigen Zweck als Zugriffspunkt zu einem Objekt zu fungieren dessen Lebensdauer nicht statisch ist. Abgesehen davon musst du doch selber das Gefühl haben dass dieses if(!staticVar) im Konstruktor grottenhässlich ist!?
Ich stehe dem if da jetzt eher neutral gegenüber ^^ um die lebensdauer jetzt genau der lebenszeit aller cube-objekte anzupassen, hätte ich jetzt in den destructor noch ein if(Cube::counter == 0) Cube::deletePrototype(); gesetzt. sobald das letzte gelöscht wird, wird auch der prototyp gelöscht, und wenn wieder ein neues erzeugt wird, erzeugt dieses wiederum einen neuen prototypen. Theoretisch passen die lebensdauern tatsächlich nicht 100% zusammen, aber da C++ kein sprachliches konstrukt hat, welches genau diesen fall abdeckt, muss man es eben "praktisch" passend machen.

Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

23

15.10.2010, 09:41

boost::shared_ptr wäre eine Lösung für die Lebensdauer des Objekts. Wobei hier auch ein banaler Resourcenmanager und Referenzzählung reichen würde.

Ich empfinde die Problemstellung als fehlgeleitet. Ganz am Ende rendert man doch keine Würfel - man rendert Modelle, die von einem Grafiker erstellt und auf Platte gespeichert wurden. Demzufolge ist die Lebenszeit der einzelnen Modelle nicht mehr ganz so scharf abzugrenzen. Modelle können sofort ab Start, erst nach einer Weile Kamerabewegung oder gar nicht benötigt werden. Und falls sie benötigt werden, dann für eine kurze Zeit, für praktisch die ganze Zeit des aktuellen Programmablaufs oder jetzt kurz mal und in ner halben Stunde wieder. Je nachdem, was man für den gängigen Anwendungsfall hält, baut man sich halt die Zugriffsart verschieden.

Möglichkeiten, soweit sie mir gerade einfallen:

a) jedes Objekt trägt seine eigenen Meshdaten. Resourcentechnisch sauber, Lebenszeit des Modells ist Lebenszeit des Objekts, welches es rendert. Üblich zum Beispiel für statische unikate Szenensegmente, man denke z.B. an die Gothic-Landschaften, tödlich jedoch für Instanzen wie z.B. den Wald nebenan mit seinen zweitausend identischen Bauminstanzen.
b) Jedes Objekt bekommt die Meshdaten bei Konstruktion reingereicht. Lebenszeit der Grafikdaten ist demnach unabhängig von der der Objekte, welche sie benutzen, und ein Problem der Grafikdatenverwaltung und nicht des Objekts.
c) Jedes Objekt merkt sich nur eine serialisierbare Referenz auf die Grafikdaten und holt sie sich beim ersten Bedarf.

Fall b) und c) benötigen jeweils eine externe Verwaltung für die Grafikdaten. Ob die dann alle Grafiken vorab lädt oder erst bei Bedarf lädt, ist dann ebenso Geschmackssache. Man sollte in beiden Fällen aber für den Fall vorsorgen, dass die Grafikmodelle potentiell auch wieder aus dem Speicher verschwinden können - sei es bei einem Device Reset auf DX9-Hardware oder falls die Grafikverwaltung der Meinung ist, dass die aktuelle Mesh-Sammlung das Budget für statische Geometrie überschreitet.

Meine Empfehlung wäre also je nach Deinen Anforderungen: wenn Du eine kurzlebige Szene aus Primitiven zusammenbauen willst, nimm Weg b) - erstelle pro Primitiv einen Mesh und reiche ihn beim Erstellen der Szene in jedes Objekt rein, dass ihn rendern soll. Falls Du eher komplexere Szenen anstrebst, die von Platte geladen werden und aus einer bunten Mischung von Unikaten und Instanzen bestehen, empfehle ich eher Weg c)
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

n0_0ne

1x Contest-Sieger

  • »n0_0ne« ist der Autor dieses Themas
  • Private Nachricht senden

24

15.10.2010, 11:57

werde mir mal boost::shared_ptr ansehen.

Was die problemstellung angeht, naja normalerweise hast du recht, dass man nicht nur würfel rendert, sondern modelle. In meinem jetzigen Fall sind es aber wirklich nur primitive objekte ^^ Außerdem macht es meiner meinung nach nicht wirklich einen unterschied, ob es jetzt um einen würfel geht, oder um einen baum. Wie du schon angemerkt hast, geht es eher darum wie viele instanzen wann und wie lange gebraucht werden.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

25

15.10.2010, 14:51

Zu 3: Hier erkenn ich nicht wirklich den zusammenhang

Nun, was für Daten hält denn dein Prototype Objekt? Sicherlich VBOs und andere OpenGL Objekte (Texturen, Shader, ...). Diese sind aber an einen Context gebunden. Wenn du mit mehreren OpenGL Contexts (ohne sharing) arbeitest brauchst du auch mehrere Prototypen was mit deinem System auf keinen Fall funktioniert.

Nur noch eine Frage: gibt es in deinem szenario jetzt nur 1 cube-objekt? Weil wie würdest du jetzt sicherstellen, dass alle Cube-Objekte den von ihren kreierten CubeInstances den selben pointer auf den Prototyp mitgeben? Meiner meinung nach sind wir dann wieder bei der ausgangssituation.

Es gibt so viele Cube Objekte wie es braucht. Die Cube Objekte sind die Prototypen (auch wenn die Bezeichnung "Prototyp" hier etwas unglücklich gewählt ist). Wenn du nur einen willst hast du nur ein Cube Objekt.

Theoretisch passen die lebensdauern tatsächlich nicht 100% zusammen, aber da C++ kein sprachliches konstrukt hat, welches genau diesen fall abdeckt, muss man es eben "praktisch" passend machen.

Doch, C++ hat ein sprachliches Konstrukt das den Fall abdeckt dass man die Lebensdauer von Objekten selbst bestimmen muss: new und delete. Aber worauf ich hier eigentlich hinauswollte: Ich finde deine Lösung packt das Problem an der falschen Seite an. In deiner Lösung kann man wann immer man lustig ist einen Cube erzeugen und wenn es der erste war werden intern irgendwie die erforderlichen Daten geladen. Wenn dies zu einem Zeitpunkt geschieht wo es noch gar keinen Sinn macht einen Cube zu erzeugen hat man eben Pech gehabt. Der Punkt ist aber man kann Cubes machen auch wenn es noch gar keinen Sinn macht. In meinem Vorschlag ist dies rein systembedingt schon unmöglich. Du kannst erst CubeInstances erzeugen wenn du ein Cube Objekt hast, und das Cube Objekt kannst du erst erzeugen wenn alles was nötig ist vorhanden ist (OpenGL Context etc.). Das ganze ist imo ein viel natürlicherer Ansatz und kommt daher auch ohne so Hacks wie deinen statischen Prototyp Pointer aus. Was du haben willst ist ein Modell das eben mehrfach instantiert werden kann. Warum das dann nicht auch auf Objektebene so modellieren? Schrompf hat genau das alles ja ebenfalls nochmal ausführlich erklärt und sein Vorschlag b ist im Prinzip genau das was ich auch schon vorgeschlagen habe. Was auch immer du tust, ich würde dir sehr empfehlen hier von globalen oder statischen Variablen oder Singletons großen Abstand zu nehmen. Aber natürlich steht es dir frei die entsprechenden Erfahrungen selber zu sammeln.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (15.10.2010, 14:58)


n0_0ne

1x Contest-Sieger

  • »n0_0ne« ist der Autor dieses Themas
  • Private Nachricht senden

26

15.10.2010, 16:17

Es gibt so viele Cube Objekte wie es braucht. Die Cube Objekte sind die Prototypen (auch wenn die Bezeichnung "Prototyp" hier etwas unglücklich gewählt ist). Wenn du nur einen willst hast du nur ein Cube Objekt.
OK, vielleicht solltest du das kurz irgendwie in Code erklären. Meine Klasse Prototype enthält wie du schon erkannt hast VBOs u.a. Angenommen ich will jetzt 5 Cubes rendern, die sich in position, rotation und skalierung unterscheiden. Dann erstelle ich 5 von "deinen" Cube-Objekten und mache die passenden transformationen darauf (d.h. auf die jeweilige modelview-matrix des cubes). Wenn ich das ganze jetzt rendern will, rufe ich Cube.createInstance(Prototype* p, ModelviewMatrix m) (oder so ähnlich) für alle 5 Cubes auf und dann wiederum CubeInstance.render() auf die 5 generierten CubeInstances. Meine Frage ist jetzt, wie stelle ich sicher, dass alle 5 Cube-Objekte den selben Prototype* p als parameter benutzen?

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

27

15.10.2010, 18:14

Cube ist der Prototype. Cube kapselt den Prototype nach außen. Du machst nur Cube.createInstance(ModelviewMatrix m).

n0_0ne

1x Contest-Sieger

  • »n0_0ne« ist der Autor dieses Themas
  • Private Nachricht senden

28

15.10.2010, 19:02

Irgendwie kommts mir vor, als würden wir aneinander vorbei reden ^^

Es ist also so, dass es viele Cube-Objekte geben kann, wirklich sinnvoll ist aber nur eins. Weil es ja ansonsten auch wieder mehrere Prototypen, sprich VBOs mit dem selben Inhalt geben würde. Ist das jetzt so richtig? ^^ Man kann Cube also als eine Art "Fabrik" verstehen.

Wenn das jetzt so stimmt, dann bräuchte ich aber doch eigentlich noch eine weitere Klasse, auf die ich dann die transformationen anwenden kann. Irgendwo muss ja die ModelviewMatrix gespeichert werden, oder?

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

29

15.10.2010, 23:03

Man kann Cube also als eine Art "Fabrik" verstehen.

Bingo

Wenn das jetzt so stimmt, dann bräuchte ich aber doch eigentlich noch eine weitere Klasse, auf die ich dann die transformationen anwenden kann. Irgendwo muss ja die ModelviewMatrix gespeichert werden, oder?

Die Modelview Matrix wirst du sinnvollerweise nicht in den Cube Instanzen speichern, oder willst du dass jede Instanz aus einer anderen Perspektive gerendert wird? Wenn dann macht es Sinn nur eine Model Transformation in den Instanzen zu speichern. Die Kamera (also die ViewProjection Matrix) würde ich der Draw Methode übergeben. In diesem Fall musst du im Beispiel unten die CubeInstance Klasse öffentlich zugänglich machen und der eben eine entsprechende Matrix mitgeben.

Hier mal etwas Code, vielleicht wirds dann klarer wie ich mir das vorstell:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// RenderObject.h

class RenderObject
{
public:
  ...
  virtual void Draw(const matrix& ModelViewProjection) = 0;
  ...
};



// Cube.h

class Cube
{
private:
  whatever data;
public:
  ...
  RenderObject* CreateInstance(const vector3& size);
  ...  
};



// Cube.cpp

namespace
{
  class CubeInstance : public RenderObject
  {
  private:
    whatever& data;
    matrix scaling;
  public:
    CubeInstance(whatever& data, const vector3& size)
      : data(data),
        scaling(size)
    {
    }

    void Draw(const matrix& ModelViewProjection)
    {
      ...
    }

  };
}


RenderObject* Cube::CreateInstance(const vector3& size)
{
  return new CubeInstance(data, size);
}

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »dot« (15.10.2010, 23:14)


n0_0ne

1x Contest-Sieger

  • »n0_0ne« ist der Autor dieses Themas
  • Private Nachricht senden

30

16.10.2010, 08:51

Also im Prinzip will ich schon, dass jeder Cube aus einer anderen Perspektive gezeichnet wird ^^ Der Prototype (VBO) enthält nur den standardwürfel, mit der modelview-matrix sag ich OpenGL (weiß nicht, ob ModelView matrix in DirectX vielleicht was anderes bezeichnet) was er mit den standard-koordinaten machen soll, z.B. Skalieren. Das mach ich jetzt auch schon so und es funktioniert eigentlich recht gut. Die perspektivische projektion im eigentlichen sinn (glMatrixMode(GL_PROJECTION)) mach ich aber nur 1 mal.

Edit: abgesehen davon wäre es in deinem beispielcode eben der vector3 size, der sollte sinnvollerweise ja auch irgendwie einer art cube-objekt zugeordnet sein. einfach nur eine reihe von vectoren zu verwalten und daraus dann am ende einen cube zu bauen finde ich nicht sehr intuitiv. in deinem design könnte man den vector aber weder der klasse RenderObject, noch Cube oder CubeInstance zuordnen.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »n0_0ne« (16.10.2010, 10:22)


Werbeanzeige