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.