Problem mit Interfaces



  • Hallo,

    ich habe aktuell ein Problem mit Interfaces.
    Mein Ziel ist es OpenGL und Vulkan zu kapseln. Später soll dann anhand einer Konfiguration zwischen Vulkan und OpenGL ausgewählt werden. Dazu nutze ich das Adapter Pattern. Ziel ist, dass ein Mesh mehrere Vertex und Fragment Shader zugewiesen bekommen kann. Ich bekomme die Fehlermeldung 'ICBaseFragmentShader' cannot instantiate abstract class und 'ICBaseVertexShader' cannot instantiate abstract class. Ich hab schon mehrere Sachen probiert wie zum Beispiel die Parameter von CVulkanBaseMesh::AddvertexShader und -AddFragmentShader mit Pointer oder Referenzen auszustatten. Aber es will einfach nicht funktionieren.
    Benutze ich evtl. Interfaces falsch? und gibt es eine andere Architektur, welche eventuell besser dafür geeignet ist.

    Interface ICBaseMesh

    class ICBaseMesh
    {
    public:
    	...
    	virtual void AddVertexShader(ICBaseVertexShader& shader) = 0;
    	virtual void AddFragmentShader(ICBaseFragmentShader& shader) = 0;
    	virtual std::vector<ICBaseVertexShader> getVertexShader() = 0;
    	virtual std::vector<ICBaseFragmentShader> getFragmentShader() = 0;
    };
    

    CVulkanBaseMesh.h

    class CVulkanBaseVertexShader;
    class CVulkanBaseFragmentShader;
    
    class CVulkanBaseMesh : public ICBaseMesh
    {
    public:
    	void CreateMesh(std::vector<Vertex> vertices);
    	std::vector<Vertex> GetVertices();
    	void AddVertexShader(CVulkanBaseVertexShader* shader);
    	void AddFragmentShader(CVulkanBaseFragmentShader* shader);
    	std::vector<ICBaseVertexShader> getVertexShader();
    	std::vector<ICBaseFragmentShader> getFragmentShader();
    private:
    	std::vector<Vertex> vertices;
    	std::vector<ICBaseVertexShader> vertexShader;
    	std::vector<ICBaseFragmentShader> fragmentShader;
    };
    

    CVulkanBaseMesh.cpp

    #include "VulkanBaseMesh.h"
    #include "VulkanBaseFragmentShader.h"
    #include "VulkanBaseVertexShader.h"
    
    void CVulkanBaseMesh::CreateMesh(std::vector<Vertex> vertices)
    {
    	this->vertices = vertices;
    }
    
    std::vector<Vertex> CVulkanBaseMesh::GetVertices()
    {
    	return this->vertices;
    }
    
    void CVulkanBaseMesh::AddVertexShader(CVulkanBaseVertexShader* shader)
    {
    	this->vertexShader.push_back(*shader);
    }
    
    void CVulkanBaseMesh::AddFragmentShader(CVulkanBaseFragmentShader* shader)
    {
    	this->fragmentShader.push_back(*shader);
    }
    
    std::vector<ICBaseVertexShader> CVulkanBaseMesh::getVertexShader()
    {
    	return this->vertexShader;
    }
    
    std::vector<ICBaseFragmentShader> CVulkanBaseMesh::getFragmentShader()
    {
    	return this->fragmentShader;
    }
    

    Gruß

    Pixma



  • std::vector<ICBaseVertexShader> vertexShader; nimmt Kopieen von ICBaseVertexShader auf. Du musst Pointer verwenden.



  • vielen Dank, ich werde es dann morgen implementieren.

    edit: ich hab mich nun doch noch einmal dran gesetzt.
    Nun habe ich std::vector<ICBaseFragmentShader> fragmentShader; in std::vector<ICBaseFragmentShader> *fragmentShader; geändert und mit dem vertexShader hab ich das genauso gemacht.
    Nun hab ich

    die Methoden angespasst:

    std::vector<ICBaseVertexShader> CVulkanBaseMesh::getVertexShader()
    {
    	return *vertexShader;
    }
    

    und

    void CVulkanBaseMesh::AddVertexShader(CVulkanBaseVertexShader* shader)
    {
    	(*vertexShader).push_back(*shader);
    }
    

    Jedoch bekomme ich nun folgenden Fehler den ich nicht verstehe: 'void std::vector<ICBaseFragmentShader,std::allocator<_Ty>>::push_back(const ICBaseFragmentShader &)': cannot convert argument 1 from 'CVulkanBaseFragmentShader **' to 'ICBaseFragmentShader &&' graphics D:\Repository\0ad\source\base_renderer\VulkanBaseMesh.cpp 24

    Sorry, dass ich noch mal um Hilfe bitte, aber ich habe glaube noch paar Lücken mit Pointer und Referenzen

    Gruß

    Pixma



  • Dein Vector muss so aussehen:

    std::vector<ICBaseVertexShader*> vertexShader;
    

    Jetzt kannst du dort pointer auf ein ICBaseVertexShader ablegen.



  • cool danke, nun funktioniert es 🙂



  • Kurze Frage zum Design, abseits von C++:

    Ist abgesehen von den hier eventuell zugeordneten, API-spezifischen Shadern das Konzept eines "Mesh" nicht für jede Grafik-API dasselbe? Eine Liste aus Vertices und ggfs. eine Liste mit Vertex-Indizes, welche die Dreiecke definiert. Wenn diese mit den korrekten Datentypen linear im Speicher vorliegen, kann man meines Wissens solche Speicherbereiche unverändert an jede gängige Grafik-API verfüttern. Auch sind nicht grafikbezogene "Meshes" denkbar, wie z.B. solche, die nur der Kollisionserkennung dienen.

    Worauf ich hinaus will ist, ob es wirklich Sinn macht, von einem "OpenGL-Mesh" und einem "Vulkan-Mesh" zu sprechen oder ob es nicht günstiger wäre, die Unterscheindung nur bei den Shadern zu machen (obwohl man in OGL wie auch in Vulkan einheitliche SPIR-V-Shader verwenden könnte).

    Ich persönlich würde sogar so weit gehen, die Shader komplett vom Konzept des "Mesh" zu trennen und diese Verbindung erst eine Hierarchieebene höher bei einem "Modell" zu machen, unter dem ich hier eine Ansammlung von Meshes sowie den einzelnen Meshes zugeordneten (via Komposition) Materialien (Parameter für Materialeigenschaften, Texturen und zugeordnete Fragment Shader) und Geometrie-Transformationen (Vertex-Shader) verstehe.

    Bei deinem gezeigten Code schimmert nur sehr wenig vom Gesamtkonzept durch, und ich will dir gewiss nicht aufgrund so weniger Informationen in dein Design hineinreden. Das soll nur eine Anregung sein, ob du mit so einer Klassenhierarchie nicht vielleicht unnötig schon früh die Flexibilität des Systems einschränkst.

    Gruss,
    Finnegan



  • Hallo Finnegan,

    danke für deine Anmerkung.
    Aktuell bin ich garnicht mehr so sicher ob meine jetzige Architektur so gut geeignet ist.
    Also soweit ich weiß, weichen Vertex Objekte zwischen OpenGL und Vulkan schon ab, oder?
    Meine Vertex Struktur hat folgende Struktur:

    struct Vertex
    {
    	CVector2D pos;
    	CVector3D color;
    
    	static VkVertexInputBindingDescription getBindingDescription()
    	{
    		...
    	}
    
    	static std::vector<VkVertexInputAttributeDescription> getAttributeDescriptions()
    	{
    		...
    	}
    };
    

    Aktuell hatte ich das so gedacht, dass es pro API eine unterschiedliche Mesh, Shader und Renderer Klasse gibt. Die Renderer Klasse ist für die Grundfunktionalität zuständig, initalisieren der API und Zeichnen. Aktuell hatte ich noch geplant dass die Renderer Klasse eine Methode applyMesh(CVulkanBaseMesh* mesh), welches das spezielle Mesh entgegennimmt und an Vulkan weiterleitet. Aber Shader könnte man glaub schon komplett trennen. Meine VulkanBaseFragmentShader hat folgenden Aufbau:

    class CVulkanBaseFragmentShader : public ICBaseFragmentShader
    {
    public:
    	void CreateFragmentShader(std::string name, std::string path);
    	std::string GetName();
    	std::string GetPath();
    private: 
    	std::string name;
    	std::string path;
    };
    

    Die Klasse CVulkanBaseVertexShader ist genauso aufgebaut.
    Also ich bin mir aktuell gar nicht mehr so sicher, hmm was würdest du eventuell an meiner Architektur optimieren.
    Des Weiteren könntet ihr mir noch ein gutes C++ Buch empfehlen in Richtung Pointer und Polymorphie?
    Ich glaube da fehlt mir noch einiges an Wissen.

    edit: wegen der Mesh Klasse muss ich mir noch einmal Gedanken machen. Vermutlich könnte ich da das Interface entfernen. Für die Shader habe ich bereits ein Interface, das müsste also ohne Probleme funktionieren. Die einzige Überlegung ist eventuell noch ein Interface IBaseVertex zu erstellen, weil der Vulkan Vertex noch spezifische getBindingDescription() und getAttributeDescriptions() Mathoden hat. Dann könnt ich je nach dem dem VulkanBaseRender oder OpenGLBaseRender Objekt über die Methode ApplyMesh(CMesh mesh) das Mesh an Vulkan oder OpenGL weiterleiten.

    Gruß

    Pixma



  • @Pixma sagte in Problem mit Interfaces:

    Also soweit ich weiß, weichen Vertex Objekte zwischen OpenGL und Vulkan schon ab, oder?

    Die Objekte, mit denen Geometriedaten jeweils gehandhabt werden, weichen schon ab. Bei OGL kann das z.B. ein GLuint-Handle auf ein VBO sein, in D3D11 ist das z.B. ein ID3D11Buffer-Objekt.

    Wo ich jedoch drauf hinaus will sind die eigentlichen Geometriedaten selbst, wie man sie letztendlich über diese Buffer-Objekte aus dem Arbeitsspeicher in den Videospeicher lädt. Diese sind für alle diese Grafik-APIs simple, zusammenhängende Speicherbereiche in denen die floats der Vertices, der uv-Koordinaten, Normalen, die Integer der Dreiecks-Indizes oder was auch immer du sonst noch für Daten in deinen Shadern verarbeiten willst, direkt hintereinander im Speicher liegen.

    Ich meine hiermit konkret deinen vertices-Member vom Typ std::vector<Vertex>. Falls die Klasse Vertex Standard Layout hat, dann lassen sich diese Vertices z.B. in OGL so direkt aus dem std::vector in den Videospeicher laden:

    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
    

    Mit den anderen Grafik-APIs läuft das analog, nur eben mit entspechend anderen Funktionen. Das was deine Mesh-Klasse also im Kern ausmacht, der vertices-Vektor, ist also nicht API-spezifisch.

    Meine Vertex Struktur hat folgende Struktur:

    Auch hier würde ich die API-spezifischen Funktionen aus der Vertex-Klasse herauslösen und an anderer Stelle unterbringen. Ein Vertex ist ein so grundlegendes Konzept, dass man es nicht an eine bestimmte API koppeln sollte. Denk an das Beispiel mit den vereinfachten geometrischen Objekten für die Kollisionserkennung, das ich genannt habe. Diese könnte z.B. nur auf der CPU laufen, völlig unabhängig von irgendeiner Grafik-API sein, und dennoch mit Vertex-Objekten arbeiten. Oder denk an ein Server-Programm, welches vielleicht nur als Konsolenanwendung geschrieben wurde, aber dennoch geometrische Kollisionen mithilfe von Vertex-Objekten prüft.

    Bezüglich der Vertex-Farbe: Dese ist keine grundlegende Eigenschaft eines Vertex, daher würde ich die Vertex-Farben eher in einem separaten Array speichern. OGL und andere APIs bieten die Möglichkeit, zusätzliche Vertex-Attribute wie eben Farben oder auch Normalen und uv-Koordinaten für Texturen in getrennten "Buffern" zu definieren. Das ist insofern flexibler, als dass man nicht für jedes Mesh diese Attribute benötigt (vielleicht wird die Farbe allein durch die Textur definiert, dann ist eine Vertex-Farbe überflüssig).

    Aktuell hatte ich das so gedacht, dass es pro API eine unterschiedliche Mesh, Shader und Renderer Klasse gibt. Die Renderer Klasse ist für die Grundfunktionalität zuständig, initalisieren der API und Zeichnen. Aktuell hatte ich noch geplant dass die Renderer Klasse eine Methode applyMesh(CVulkanBaseMesh* mesh), welches das spezielle Mesh entgegennimmt und an Vulkan weiterleitet.
    ...
    Also ich bin mir aktuell gar nicht mehr so sicher, hmm was würdest du eventuell an meiner Architektur optimieren.

    Das kann sehr schnell beliebig komplex werden, aber generell würde ich empfehlen, alles API-spezifische möglichst schnell auf der untersten Hierarchieebene der Software-Architektur loszuwerden. So etwas wie Modelle, Meshes, Vertices, Vertex-Farben und Texturen und sogar das Konzept von "Materialien", das die Auswahl der passenden Shader beeinflusst, sind sehr allgemeine Konzepte und sollten daher alle API-agnostisch sein.

    Wenn du dich gerade erst in Grafikprogrammierung einarbeitest, dann brauchst du ohnehin noch einiges an Erfahrung. Es ist also nicht verkehrt erstmal "einfach mal zu machen", es wird ohnehin nicht beim ersten mal alles perfekt, aber man lernt viel dabei 😉

    Auch gibt es viele verscheidene Ansätze, wie man so ein System umsetzen kann. Wenn du absolut keine Ideen hast, dann überlege doch erstmal, wie du dein System am liebsten verwenden würdest, wenn es fertig ist. Das könnte vielleicht irgendwo im Rendering-Loop so aussehen:

    renderer.draw(transformation, mesh, vertex_colors, material);
    

    Wobei renderer ein OpenGL- oder ein Vulkan-Renderer sein kann und hier das einzige Objekt ist, das wirklich API-spezifisch ist. transformation, mesh, vertex_colors und material können so wie sie sind an einen beliebigen "Renderer" übergeben werden.

    transformation ist hier die Model/View-Transformation, die angewendet werden soll, mesh repräsentiert das Dreiecksnetz, das du darstellen willst, vertex_colors ist eine Liste mit Farben für jeden Vertex des Mesh und material, nunja, das verallgemeinerte "Material" welches das dargestellte Mesh haben soll:

    Dieses "Material" könnte z.B. alle Materialeigenschaften bündeln, die du mit deinem System darstellen möchtest. Das können z.B. Farbtexturen sein, Normal-Maps, Specular-Maps, materialspezifische Parameter wie z.B. eine Grundfarbe, ein Spiegelexponent für spiegelnde Beleuchtung, oder auch welcher Typ (nicht der Shader selbst!) von Fragment-Shader für das Material verwendet werden soll. Alles vorerst noch API-agnostisch.

    Die eigentliche Auswahl des zu verwendeten Shaders findet dann innherhalb der API-spezifischen Renderer-Klasse statt. Diese entscheidet dann aufgrund der Materialeigenschaften, welcher Shader verwendet werden soll.

    Das nur als Vorschlag für einen ersten halbwegs flexiblen Ansatz. Das ist noch nicht der Wahrheit letzter Schluss. Es soll nur als grobe Richtung dienen in der man erstmal anfangen und dabei lernen kann es besser zu machen (!) 😉

    Des Weiteren könntet ihr mir noch ein gutes C++ Buch empfehlen in Richtung Pointer und Polymorphie?
    Ich glaube da fehlt mir noch einiges an Wissen.

    Bin wahrlich kein Buchexperte, das klingt als suchtest du nach einem grundlegenden C++-Lehrbuch. Das einzige was ich dazu sagen kann ist, dass du wahrscheinlich mit einer aktuellen Ausgabe "Der C++-Programmierer" von Ulrich Breymann nicht viel falsch machen kannst. Ich denke da haben aber vielelicht noch andere Leute hier weitere Empfehlungen.

    Gruss,
    Finnegan



  • Hi,

    wow, vielen Dank.
    ich hab mich jetzt noch mal in die Thematik bezüglich Material detaillierter eingearbeitet.
    Aber jetzt ist mir das ganze klarer. Also ich werde nun nur noch den Renderer in ein Interface packen und den Rest entkoppeln. Die Vertex Struktur werde ich dann von den Vulkan Methoden komplett entkoppeln. So mache ich aus der Struktur Vertex eine Klasse VulkanVertex mit den methoden setVertex(...), getVertex(), getBindingDescription() und getAttributeDescriptions(). Die eigentliche Vertex Klasse konstruiere ich API unabhängig. Diese hat nurnoch die reinen Vertices und Color trenne ich dann auch komplett vom Vertex.
    Ehrlich gesagt hab ich mir, dass jetzt in der Hinsicht alles ein wenig komplizierter vorgestellt, weil ich dachte, dass jeder Hersteller (OpenGL, Vulkan, DirectX) sein eigenes Süppchen kocht. Ich vermute deshalb mal, dass generell die einzelnen API's jetzt nicht so unterschiedlich sind, wegen der Render Pipeline Architektur der Grafikkarten.
    Naja wie gesagt, ich muss mir da aber noch ein paar Gedanken machen, wie ich das am besten aufteile in die Klassen. Ich denke es ist besser wenn man sich am Anfang Gedanken über die Softwarearchitektur macht, als wenn es dann im späteren Verlauf Probleme gibt und außerdem hab ich ja Zeit. Mir läuft die Zeit ja nicht davon, bin in Sachen Grafikprogrammierung eh noch aktuell ein Rookie 🙂
    Vielen Dank für die Buchempfehlung, mir geht es hauptsächlich wie gesagt um Pointer und Polymorphie. Aber ein Cpp Buch wäre generell mal ganz gut auch als Nachschlagewerk.

    Gruß

    Dennis


Log in to reply