EGL, OpenGL-ES und Linux
« | 27 Jun 2021 | »Der kleinste gemeinsame Nenner aller Linux
Systeme in Sachen OpenGL ist - so
mein aktueller Stand - OpenGL ES 1.0 (OpenGL for Embedded Systems,
auch GLES oder EGL genannt), welches ein Subset von OpenGL 1.3 ist.
Genau hier schließt sich der Kreis, wie man eine GL-Codebase ohne (große)
Anpassungen auf allen Plattformen zum Laufen bekommen kann.
(Selbstverständlich geht das nur ohne Shader
einzusetzen.)
Sowohl unsere Desktop und Embedded Linux Varianten bis hin zu Android bieten EGL Support, also ist klar, dass diese Anbindung ins GATE Projekt muss.
Da OpenGL ES eine Teilmenge von OpenGL 1.3 ist, kann man sagen:
Ein GLES-API basiertes Programm läuft auch unter OpenGL ohne Anpassungen. OpenGL-API basierte Programme laufen jedoch nicht unter GLES Umgebungen, wenn sie OpenGL-only APIs benutzen.
Die Sache ist also einfach: Wir müssen bzw. dürfen nur jene APIs nutzen,
die in GLES und OpenGL gemeinsam vorkommen. Dann laden wir die entsprechenden
GL Libraries zur Laufzeit und holen nur die notwendigen Funktionszeiger
heraus.
Naja … leider nicht ganz … aber grundsätzlich doch …
Kein 64 Bit auf GLES
Es ist ein bisschen kompliziert. Die GL APIs sind oft mehrfach vorhanden
und zwar für die Datentypen: int
, float
und double
. Bei int
handelt
es sich um Festkommazahlen,
die in einen Integer abgebildet sind, und dann
noch 32 bit und 64 bit Gleitkomma-Varianten.
Bei GLES gibt es mehrere Profile, wobei double
auf meinen Plattformen nie
implementiert war.
Die Integer-Variante ist im Zahlenbereich recht limitiert und somit sind
die float
Funktionen das einzige, was man plattformübergreifend nutzen kann.
Also wenn man eine Farbe setzen möchte, wählt man von den verfügbaren
glColor4d
(double), glColor4f
(float), glColor4i
(int),
glColor4s
(short) einfach nur glColor4f
und kann sich recht sicher sein,
dass man damit am weitesten kommen wird (außer man hat wirklich eine Plattform
erwischt, die gar keine Gleitkommazahlen kann).
Ein bisschen “blöd” wird es mit einer Hand voll Funktionen, die unter OpenGL
nur eine double
-only und unter GLES nur eine float
-only Implementierung
haben. Bei diesen musste ich bei den Funktionsdeklarationen “#ifdef
“-en und
einmal float
und einmal double
als Parameter wählen. In der Anwendung
selbst nutze ich dann nur float
welches unter OpenGL automatisch in einen
double
gecastet wird.
Folgende Funktionen sind in meinem Anwendungsbereich betroffen und haben unter
GLES ein f
am Ende angefügt, während OpenGL sie ohne Suffix mit double
füttert:
Richtige Lib laden
Unter Windows lädt man seit NT 4.0
einfach opengl32.dll
und alles ist gut.
Linux und Android
machen einem das etwas schwerer, indem nicht überall alle Links gesetzt sind,
oder die Libs grundsätzlich anders heißen.
Mit:
libGLESv1_CM.so
libGL.so.1.2
libGL.so.1
libGL.so
habe ich aber eine Möglichkeitensammlung, wo meist ein Treffer dabei ist. Aber ich wette, dass mit weiteren Plattformen weitere Libnamen möglich sein werden.
GLES Ablauf
Es kommt natürlich darauf an, ob man auf UI Fenster oder in Bitmaps zeichnet, aber folgender Ablauf ist notwendig, um per GLES den Bildschirm zu erhellen:
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY)
Display-Handle holeneglInitialize(egl_display, &egl_version_major, &egl_version_minor)
EGL/GLES initialisieren (und Versionsnummer abholen)eglGetConfigs(egl_display, all_configs_array, all_configs_count, &configs_returned)
Alle unterstützten Konfigurationsprofile ausleseneglChooseConfig(egl_display, attrib_list, configs_array, configs_array_capacity, &configs_returned)
Zu einer Liste von gewünschten Attributen alle dazu passenden Konfigurationsprofile auslesen. Ich nutze für Fenstergrafik folgendes:1static EGLint gate_egl_attribs_window[] = 2{ 3 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 4 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT, 5 EGL_RED_SIZE, (EGLint)5, 6 EGL_GREEN_SIZE, (EGLint)5, 7 EGL_BLUE_SIZE, (EGLint)5, 8 EGL_ALPHA_SIZE, (EGLint)0, 9 EGL_DEPTH_SIZE, (EGLint)16, 10 //EGL_STENCIL_SIZE, (EGLint)0, 11 EGL_NONE 12};
Das sind zwar nur 16-bit Farbtiefe aber funktionieren zumindest immer.
egl_context = eglCreateContext(egl_display, configs_array[0], NULL, NULL)
Gewünschtes Profil laden, das erste gelieferte Profil voneglChooseConfig
hat bei mir immer auf Anhieb funktioniert.egl_surface = eglCreateWindowSurface(egl_display, configs_array[0], native_window, NULL)
Nun wird die Zeichenoberfläche vorbereitet. Dafür braucht man dasnative_window
der Plattform. Bei X11 wäre das ein Fenster, das mitXCreateWindow()
erzeugt wurde und bei Android NDK nutzt man dasANativeWindow
welches man über den Event-CallbackonNativeWindowCreated
empfangen hat.eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context)
Am Ende werden “display”, “surface” und “context” gemeinsam aktiviert und nun kann man die üblichenglXYZ()
Funktionen nutzen um darauf zu zeichnen.
Tja und wenn man genug von GLES hat, muss man die Bindung und die Objekte einfach nur freigeben:
eglMakeCurrent(egl_display, NULL, NULL, EGL_NO_CONTEXT)
eglDestroySurface(egl_display, egl_surface)
eglDestroyContext(egl_display, egl_context)
Fazit
Im Großen und Ganzen war es das auch schon.
Ein offenere Brocken ist natürlich das “Event-Handling”, also wie eine
Mausbewegung oder ein Tastendruck da noch hineinspielt und das ist natürlich
wieder extrem plattformspezifisch.
In Linux lässt man eine X11 XEvent
Schleife um das native_window
kreisen und im Android NDK darf man die
entsprechenden AInputEvent
s empfangen um z.B. AKeyEvent
und
AMotionEvent
herauszupicken.
Das Schöne dabei ist jedoch, dass man nach der Initialisierung nicht mehr
an die Plattform denken muss. Windows, Linux und Android können mit den
gleichen Zeichenbefehlen gefüttert werden, die (solange alles float
ist)
brav abgearbeitet und auf dem Anzeigegerät sichtbar werden.