5 Stimmen

Plattformübergreifender Renderer in OpenGL ES

Ich schreibe einen plattformübergreifenden Renderer. Ich möchte es auf Windows, Linux, Android, iOS verwenden.

Glauben Sie, dass es eine gute Idee, absolute Abstraktion zu vermeiden und schreiben Sie es direkt in OpenGL ES 2.0 ist?

Soweit ich weiß, sollte ich in der Lage sein, es auf PC gegen Standard-OpenGL zu kompilieren, mit nur einer kleinen Änderung im Code, der den Kontext und die Verbindung zum Fenstersystem behandelt.

9voto

Nicol Bolas Punkte 409659

Glauben Sie, dass es eine gute Idee, absolute Abstraktion zu vermeiden und schreiben Sie es direkt in OpenGL ES 2.0 ist?

Ihre Hauptschwierigkeiten dabei sind die Teile der ES 2.0-Spezifikation, die nicht wirklich mit OpenGL 2.1 übereinstimmen.

Man kann zum Beispiel nicht einfach ES 2.0-Shader durch einen GLSL 1.20-Desktop-Compiler schieben. In ES 2.0 verwenden Sie Dinge wie die Angabe der Präzision; dies sind illegale Konstrukte in GLSL 1.20.

Vous peut jedoch #define um sie herum, aber das erfordert ein paar manuelle Eingriffe. Sie müssen eine #ifdef in die Shader-Quelldatei. Es gibt Tricks für die Shader-Kompilierung, die dies etwas einfacher machen.

Da GL ES einen völlig anderen Satz von Erweiterungen verwendet (auch wenn einige davon Spiegelungen und Teilmengen von Desktop-GL-Erweiterungen sind), sollten Sie dies tun.

Jeder GLSL-Shader (Desktop oder ES) muss eine "Präambel" haben. Das erste nicht-kommentierte Element in einem Shader muss ein #version Erklärung. Zum Glück für Sie ist die Version zwischen Desktop GL 2.1 und GL ES 2.0 identisch: #version 1.20 . Das Problem ist, was danach kommt: die #extension Liste (falls vorhanden). Dadurch werden die für den Shader erforderlichen Erweiterungen aktiviert.

Da GL ES andere Erweiterungen als Desktop-GL verwendet, müssen Sie diese Erweiterungsliste ändern. Und da die Chancen gut stehen, dass Sie mehr GLSL ES-Erweiterungen als Desktop-GL 2.1-Erweiterungen benötigen werden, werden diese Listen nicht nur 1:1-Zuordnung, sondern völlig unterschiedliche Listen sein.

Ich schlage vor, die Möglichkeit zu nutzen, GLSL-Shadern mehrere Strings zu geben. Das heißt, Ihre eigentlichen Shader-Dateien nicht keine Präambel haben. Sie sólo haben die eigentlichen Definitionen und Funktionen. Der Hauptteil des Shaders.

Wenn Sie mit GL ES arbeiten, haben Sie eine globale Präambel, die Sie am Anfang des Shaders anbringen. In Desktop-GL wird eine andere globale Präambel verwendet. Der Code würde wie folgt aussehen:

GLuint shader = glCreateShader(/*shader type*/);
const char *shaderList[2];
shaderList[0] = GetGlobalPreambleString(); //Gets preamble for the right platform
shaderList[1] = LoadShaderFile(); //Get the actual shader file
glShaderSource(shader, 2, shaderList, NULL);

Die Präambel kann 矢張り enthalten eine plattformspezifische #define . Benutzerdefiniert, versteht sich. Auf diese Weise können Sie #ifdef Code für verschiedene Plattformen.

Es gibt noch weitere Unterschiede zwischen den beiden. Zum Beispiel, während die gültige ES 2.0 Textur-Upload-Funktion aufruft wird im Desktop-GL 2.1 gut funktionieren, sind sie nicht unbedingt optimal. Dinge, die auf Big-Endian-Rechnern wie allen mobilen Systemen problemlos hochgeladen werden können, erfordern auf Little-Endian-Desktop-Rechnern einige Bitumstellungen durch den Treiber. Sie sollten also eine Möglichkeit haben, unterschiedliche Pixelübertragungsparameter für GL ES und Desktop-GL festzulegen.

Außerdem gibt es verschiedene Erweiterungen in ES 2.0 und Desktop GL 2.1, die Sie nutzen sollten. Während viele von ihnen versuchen, einander zu spiegeln (OES_framebuffer_object ist eine Teilmenge von EXT_framebuffer_object), können Sie in Konflikt mit ähnlichen "nicht ganz eine Teilmenge" Probleme wie die oben genannten laufen.

3voto

Maurizio Benedetti Punkte 3501

Meiner bescheidenen Erfahrung nach ist der beste Ansatz für diese Art von Anforderungen, den Motor in reinem C zu entwickeln, ohne zusätzliche Schichten darauf.

Ich bin der Hauptentwickler der PATRIA 3D-Engine, die auf dem von Ihnen erwähnten Grundprinzip der Übertragbarkeit basiert, und wir haben dies erreicht, indem wir das Tool nur auf der Grundlage von Standardbibliotheken entwickelt haben.

Der Aufwand, Ihren Code dann auf den verschiedenen Plattformen zu kompilieren, ist sehr gering.

Der tatsächliche Aufwand für die Portierung der gesamten Lösung hängt von den Komponenten ab, die Sie in Ihre Engine einbetten wollen.

Zum Beispiel:


Standard C:

Motor 3D

Spiel-Logik

Spiel-KI

Physik


+


Fensterschnittstelle (GLUT, EGL usw.) - Hängt von der Plattform ab, auf jeden Fall könnte es GLUT für Desktop und EGL für mobile Geräte sein.

Human Interface - abhängig von der Portierung, Java für Android, OC für IOS, beliebige Desktop-Version

Soundmanager - abhängig von der Portierung

Marktdienste - abhängig von der Portierung


Auf diese Weise können Sie 95 % Ihrer Bemühungen nahtlos wiederverwenden.

Wir haben diese Lösung für unseren Motor übernommen, und bisher hat sich die anfängliche Investition wirklich gelohnt.

0voto

Graham Asher Punkte 1476

Hier sind die Ergebnisse meiner Erfahrungen bei der Implementierung von OpenGL ES 2.0-Unterstützung für verschiedene Plattformen, auf denen meine kommerzielle Mapping- und Routing-Bibliothek läuft.

Die Rendering-Klasse ist so konzipiert, dass sie in einem separaten Thread läuft. Sie hat einen Verweis auf das Objekt, das die Kartendaten und die aktuellen Ansichtsinformationen enthält, und verwendet Mutexe, um Konflikte beim Lesen dieser Informationen zum Zeitpunkt des Zeichnens zu vermeiden. Sie verwaltet einen Cache von OpenGL ES-Vektordaten im Grafikspeicher.

Die gesamte Rendering-Logik ist in C++ geschrieben und wird auf allen folgenden Plattformen verwendet.

Windows (MFC)

Verwenden Sie die ANGLE-Bibliothek: Binden Sie an libEGL.lib und libGLESv2.lib und stellen Sie sicher, dass die ausführbare Datei Zugriff auf die DLLs libEGL.dll und libGLESv2.dll hat. Der C++-Code erstellt einen Thread, der die Grafiken mit einer geeigneten Rate (z. B. 25 Mal pro Sekunde) neu zeichnet.

Windows (.NET und WPF)

Verwenden Sie einen C++/CLI-Wrapper, um einen EGL-Kontext zu erstellen und den C++-Rendering-Code aufzurufen, der direkt in der MFC-Implementierung verwendet wird. Der C++-Code erstellt einen Thread, der die Grafiken mit einer geeigneten Rate (z. B. 25 Mal pro Sekunde) neu zeichnet.

Windows (UWP)

Erstellen Sie den EGL-Kontext im Code der UWP-Anwendung und rufen Sie den C++-Rendering-Code über einen C++/CXX-Wrapper auf. Sie müssen ein SwapChainPanel verwenden und Ihre eigene Rendering-Schleife erstellen, die in einem anderen Thread läuft. Siehe die GLUWP Projekt für Beispielcode.

Qt unter Windows, Linux und Mac OS

Verwenden Sie ein QOpenGLWidget als Ihr Windows. Verwenden Sie den Qt OpenGL ES Wrapper, um den EGL Kontext zu erstellen und rufen Sie dann den C++ Rendering Code in Ihrer paintGL() Funktion auf.

Android

Erstellen Sie eine Renderer-Klasse, die Android.opengl.GLSurfaceView.Renderer. Erstellen Sie einen JNI-Wrapper für das C++-Rendering-Objekt. Erstellen Sie das C++-Rendering-Objekt in Ihrer onSurfaceCreated()-Funktion. Rufen Sie die Zeichenfunktion des C++-Rendering-Objekts in Ihrer onDrawFrame()-Funktion auf. Sie müssen die folgenden Bibliotheken für Ihre Renderer-Klasse importieren:

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;

Erstellen Sie eine von GLSurfaceView abgeleitete Ansichtsklasse. Richten Sie im Konstruktor Ihrer View-Klasse zunächst Ihre EGL-Konfiguration ein:

setEGLContextClientVersion(2); // use OpenGL ES 2.0
setEGLConfigChooser(8,8,8,8,24,0);

dann erstellen Sie eine Instanz Ihrer Renderer-Klasse und rufen setRenderer auf, um sie zu installieren.

iOS

Verwenden Sie die METALWinkel Bibliothek, nicht GLKit, das von Apple veraltet ist und irgendwann nicht mehr unterstützt wird.

Erstellen Sie eine Objective C++-Renderer-Klasse, um Ihre C++ OpenGL ES-Zeichenlogik aufzurufen.

Erstellen Sie eine von MGLKView abgeleitete Ansichtsklasse. Erstellen Sie in der drawRect()-Funktion Ihrer View-Klasse ein Renderer-Objekt, falls es noch nicht existiert, und rufen Sie dann dessen Zeichenfunktion auf. Das heißt, Ihre drawRect-Funktion sollte in etwa so lauten:

-(void)drawRect:(CGRect)rect
    {
    if (m_renderer == nil && m_my_other_data != nil)
        m_renderer = [[MyRenderer alloc] init:m_my_other_data];
    if (m_renderer)
        [m_renderer draw];
    }

In Ihrer Anwendung benötigen Sie eine View-Controller-Klasse, die den OpenGL-Kontext erstellt und einrichtet, indem Sie Code wie diesen verwenden:

MGLContext* opengl_context = [[MGLContext alloc] initWithAPI:kMGLRenderingAPIOpenGLES2];
m_view = [[MyView alloc] initWithFrame:aBounds context:opengl_context];
m_view.drawableDepthFormat = MGLDrawableDepthFormat24;
self.view = m_view;
self.preferredFramesPerSecond = 30;

Linux

Am einfachsten ist es, Qt unter Linux zu verwenden (siehe oben), aber es ist auch möglich, die GLFW Rahmen. Rufen Sie im Konstruktor Ihrer App-Klasse glfwCreateWindow auf, um ein Fenster zu erstellen und es als Datenelement zu speichern. Rufen Sie glfwMakeContextCurrent auf, um den EGL-Kontext aktuell zu machen, und erstellen Sie dann ein Datenmitglied, das eine Instanz Ihrer Renderer-Klasse enthält; etwa so:

m_window = glfwCreateWindow(1024,1024,"My Window Title",nullptr,nullptr);
glfwMakeContextCurrent(m_window);
m_renderer = std::make_unique<CMyRenderer>();

Fügen Sie eine Draw-Funktion zu Ihrer App-Klasse hinzu:

bool MapWindow::Draw()
    {
    if (glfwWindowShouldClose(m_window))
        return false;
    m_renderer->Draw();
    /* Swap front and back buffers */
    glfwSwapBuffers(m_window);
    return true;
    }

Ihre main()-Funktion wird dann sein:

int main(void)
    {
    /* Initialize the library */
    if (!glfwInit())
        return -1;

    // Create the app.
    MyApp app;

    /* Draw continuously until the user closes the window */
    while (app.Draw())
        {

        /* Poll for and process events */
        glfwPollEvents();
        }

    glfwTerminate();
    return 0;
    }

Shader-Inkompatibilitäten

Es gibt Inkompatibilitäten in der Shader-Sprache, die von den verschiedenen OpenGL ES 2.0-Implementierungen akzeptiert wird. Ich habe diese im C++-Code mit dem folgenden, bedingt kompilierten Code in meiner CompileShader-Funktion überwunden:

const char* preamble = "";

#if defined(_POSIX_VERSION) && !defined(ANDROID) && !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
// for Ubuntu using Qt or GLFW
preamble = "#version 100\n";
#elif defined(USING_QT) && defined(__APPLE__)
// On the Mac #version doesn't work so the precision qualifiers are suppressed.
preamble = "#define lowp\n#define mediump\n#define highp\n";
#endif

En preamble wird dann dem Shader-Code vorangestellt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X