Android Hardware OpenGLES emulation design overview =================================================== Introduction: ------------- Hardware GLES emulation in the Android platform is implemented with a mix of components, which are: - Several host "translator" libraries. They implement the EGL, GLES 1.1 and GLES 2.0 ABIs defined by Khronos, and translate the corresponding function calls into calls to the appropriate desktop APIs, i.e.: - Xgl (Linux), AGL (OS X) or WGL (Windows) for EGL - desktop GL 2.0 for GLES 1.1 and GLES 2.0 _________ __________ __________ | | | | | | HOST |TRANSLATOR |TRANSLATOR| |TRANSLATOR| HOST | EGL | | GLES 1.1 | | GLES 2.0 | TRANSLATOR |_________| |__________| |__________| LIBRARIES | | | - - - | - - - - - - - - - | - - - - - - - - - | - - - - - | | | ____v____ ____v_____ _____v____ HOST | | | | | | SYSTEM | Xgl | | GL 2.0 | | GL 2.0 | LIBRARIES |_________| |__________| |__________| - Several system libraries inside the emulated guest system that implement the same EGL / GLES 1.1 and GLES 2.0 ABIs. They collect the sequence of EGL/GLES function calls and translate then into a custom wire protocol stream that is sent to the emulator program through a high-speed communication channel called a "QEMU Pipe". For now, all you need to know is that the pipe is implemented with a custom kernel driver, and provides for _very_ fast bandwidth. All read() and writes() from/to the pipes are essentially instantaneous from the guest's point of view. _________ __________ __________ | | | | | | |EMULATION| |EMULATION | |EMULATION | GUEST | EGL | | GLES 1.1 | | GLES 2.0 | SYSTEM |_________| |__________| |__________| LIBRARIES | | | - - - | - - - - - - - - - | - - - - - - - - - | - - - - - | | | ____v____________________v____________________v____ GUEST | | KERNEL | QEMU PIPE | |___________________________________________________| | - - - - - - - - - - - - - -|- - - - - - - - - - - - - - - - | v EMULATOR - Specific code inside the emulator program that is capable of transmitting the wire protocol stream to a special rendering library or process (called the "renderer" here), which understands the format. | | PROTOCOL BYTE STREAM _____v_____ | | | EMULATOR | |___________| | | UNMODIFIED PROTOCOL BYTE STREAM _____v_____ | | | RENDERER | |___________| - The renderer decodes the EGL/GLES commands from the wire protocol stream, and dispatches them to the translator libraries appropriately. | | PROTOCOL BYTE STREAM _____v_____ | | | RENDERER | |___________| | | | +-----------------+ | +-----------------+ | | | ____v____ ___v______ ____v_____ | | | | | | HOST |TRANSLATOR |TRANSLATOR| |TRANSLATOR| HOST | EGL | | GLES 1.1 | | GLES 2.0 | TRANSLATOR |_________| |__________| |__________| LIBRARIES - In reality, the protocol stream flows in both directions, even though most of the commands result in data going from the guest to the host. A complete picture of the emulation would thus be: _________ __________ __________ | | | | | | |EMULATION| |EMULATION | |EMULATION | GUEST | EGL | | GLES 1.1 | | GLES 2.0 | SYSTEM |_________| |__________| |__________| LIBRARIES ^ ^ ^ | | | - - - | - - - - - - - - - | - - - - - - - - - | - - - - - | | | ____v____________________v____________________v____ GUEST | | KERNEL | QEMU PIPE | |___________________________________________________| ^ | - - - - - - - - - - - - - -|- - - - - - - - - - - - - - - - | | PROTOCOL BYTE STREAM _____v_____ | | | EMULATOR | |___________| ^ | UNMODIFIED PROTOCOL BYTE STREAM _____v_____ | | | RENDERER | |___________| ^ ^ ^ | | | +-----------------+ | +-----------------+ | | | ____v____ ___v______ ____v_____ | | | | | | |TRANSLATOR |TRANSLATOR| |TRANSLATOR| HOST | EGL | | GLES 1.1 | | GLES 2.0 | TRANSLATOR |_________| |__________| |__________| LIBRARIES ^ ^ ^ | | | - - - | - - - - - - - - - | - - - - - - - - - | - - - - - | | | ____v____ ____v_____ _____v____ HOST | | | | | | SYSTEM | Xgl | | GL 2.0 | | GL 2.0 | LIBRARIES |_________| |__________| |__________| (NOTE: 'Xgl' is for Linux only, replace 'AGL' on OS X, and 'WGL' on Windows). Note that, in the above graphics, only the host system libraries at the bottom are _not_ provided by Android. Design Requirements: -------------------- The above design comes from several important requirements that were decided early in the project: 1 - The ability to run the renderer in a separate process from the emulator itself is important. For various practical reasons, we plan to completely separate the core QEMU emulation from the UI window by using two distinct processes. As such, the renderer will be implemented as a library inside the UI program, but will need to receive protocol bytes from the QEMU process. The communication channel will be either a fast Unix socket or a Win32 named pipe between these two. A shared memory segment with appropriate synchronization primitives might also be used if performance becomes an issue. This explains why the emulator doesn't alter or even try to parse the protocol byte stream. It only acts as a dumb proxy between the guest system and the renderer. This also avoids adding lots of GLES-specific code inside the QEMU code base which is terribly complex. 2 - The ability to use vendor-specific desktop EGL/GLES libraries is important. GPU vendors like NVidia, AMD or ARM all provide host versions of the EGL/GLES libraries that emulate their respectivie embedded graphics chipset. The renderer library can be configured to use these instead of the translator libraries provided with this project. This can be useful to more accurately emulate the behaviour of specific devices. Moreover, these vendor libraries typically expose vendor-specific extensions that are not provided by the translator libraries. We cannot expose them without modifying our code, but it's important to be able to do so without too much pain. Code organization: ------------------ All source code for the components above is spread over multiple directories in the Android source trees: - The emulator sources are under $ANDROID/external/qemu, which we'll call $QEMU in the rest of this document. - The guest libraries are under $ANDROID/development/tools/emulator/opengl, which we'll call $EMUGL_GUEST - The host renderer and translator libraries are under $ANDROID/sdk/emulator/opengl, which we'll call $EMUGL_HOST - The QEMU Pipe kernel driver is under $KERNEL/drivers/misc/qemupipe Where $ANDROID is the top of the open-source Android source tree, and $KERNEL is the top of the qemu-specific kernel source tree (using one of the android-goldfish-xxxx branches here). The emulator sources related to this projects are: $QEMU/hw/goldfish_pipe.c -> implement QEMU pipe virtual hardware $QEMU/hw/opengles.c -> implement GLES initialization $QEMU/hw/hw-pipe-net.c -> implements the communication channel between the QEMU Pipe and the renderer library The other sources are: $EMUGL_GUEST/system -> system libraries $EMUGL_GUEST/shared -> guest copy of shared libraries $EMUGL_GUEST/tests -> various test programs $EMUGL_HOST/host -> host libraries (translator + renderer) $EMUGL_HOST/shared -> host copy of shared libraries $EMUGL_HOST/tests -> various test programs The reason the shared libraries aren't actually shared is historical: at one point both guest and host code lived in the same place. That turned out to be impractical with the way the Android SDK is branched, and didn't support the requirement that a single emulator binary be able to run several generations of Android. Translator libraries: --------------------- There are three translator host libraries provided by this project: libEGL_translator -> EGL 1.2 translation libGLES_CM_translator -> GLES 1.1 translation libGLES_V2_translator -> GLES 2.0 translation The full name of the library will depend on the host system. For simplicity, only the library name suffix will change (i.e. the 'lib' prefix is not dropped on Windows), i.e.: libEGL_translator.so -> for Linux libEGL_translator.dylib -> for OS X libEGL_translator.dll -> for Windows The source code for these libraries is located under the following path in the Android source tree: $EMUGL_HOST/host/libs/Translator/EGL $EMUGL_HOST/host/libs/Translator/GLES_CM $EMUGL_HOST/host/libs/Translator/GLES_V2 The translator libraries also use a common routines defined under: $EMUGL_HOST/host/libs/Translator/GLcommon Wire Protocol Overiew: ---------------------- The "wire protocol" is implemented as follows: - EGL/GLES function calls are described through several "specification" files, which describes the types, function signatures and various attributes for each one of them. - These files are read by a tool called "emugen" which generates C source files and headers based on the specification. These correspond to both encoding, decoding and "wrappers" (more on this later). - System "encoder" static libraries are built using some of these generated files. They contain code that can serialize EGL/GLES calls into simple byte messages and send it through a generic "IOStream" object. - Host "decoder" static libraries are also built using some of these generated files. Their code retrieves byte messages from an "IOStream" object, and translates them into function callbacks. IOStream abstraction: - - - - - - - - - - - The "IOStream" is a very simple abstract class used to send byte messages both in the guest and host. It is defined through a shared header under $EMUGL/host/include/libOpenglRender/IOStream.h Note that despite the path, this header is included by *both* host and guest source code. The main idea around IOStream's design is that to send a message, one does the following: 1/ call stream->allocBuffer(size), which returns the address of a memory buffer of at least 'size' bytes. 2/ write the content of the serialized command (usually a header + some payload) directly into the buffer 3/ call stream->commitBuffer() to send it. Alternatively, one can also pack several commands into a single buffer with stream->alloc() and stream->flush(), as in: 1/ buf1 = stream->alloc(size1) 2/ write first command bytes into buf1 3/ buf2 = stream->alloc(size2) 4/ write second command bytes into buf2 5/ stream->flush() Finally, there are also explict read/write methods like stream->readFully() or stream->writeFully() which can be used when you don't want an intermediate buffer. This is used in certain cases by the implementation, e.g. to avoid an intermediate memory copy when sending texture data from the guest to the host. The host IOStream implementations are under $EMUGL/shared/OpenglCodecCommon/, see in particular: $EMUGL_HOST/shared/OpenglCodecCommon/TcpStream.cpp -> using local TCP sockets $EMUGL_HOST/shared/OpenglCodecCommon/UnixStream.cpp -> using Unix sockets $EMUGL_HOST/shared/OpenglCodecCommon/Win32PipeStream.cpp -> using Win32 named pipes The guest IOStream implementation uses the TcpStream.cpp above, as well as an alternative QEMU-specific source: $EMUGL_GUEST/system/OpenglSystemCommon/QemuPipeStream.cpp -> uses QEMU pipe from the guest The QEMU Pipe implementation is _significantly_ faster (about 20x) due to several reasons: - all succesful read() and write() operations through it are instantaneous from the guest's point of view. - all buffer/memory copies are performed directly by the emulator, and thus much faster than performing the same thing inside the kernel with emulated ARM instructions. - it doesn't need to go through a kernel TCP/IP stack that will wrap the data into TCP/IP/MAC packets, send them to an emulated ethernet device, which is itself connected to an internal firewall implementation that will unwrap the packets, re-assemble them, then send them through BSD sockets to the host kernel. However, would it be necessary, you could write a guest IOStream implementation that uses a different transport. If you do, please look at $EMUGL_GUEST/system/OpenglCodecCommon/HostConnection.cpp which contains the code used to connect the guest to the host, on a per-thread basis. Source code auto-generation: - - - - - - - - - - - - - - The 'emugen' tool is located under $EMUGL_HOST/host/tools/emugen. There is a README file that explains how it works. You can also look at the following specifications files: For GLES 1.1: $EMUGL_HOST/host/GLESv1_dec/gl.types $EMUGL_HOST/host/GLESv1_dec/gl.in $EMUGL_HOST/host/GLESv1_dec/gl.attrib For GLES 2.0: $EMUGL_HOST/host/GLESv2_dec/gl2.types $EMUGL_HOST/host/GLESv2_dec/gl2.in $EMUGL_HOST/host/GLESv2_dec/gl2.attrib For EGL: $EMUGL_HOST/host/renderControl_dec/renderControl.types $EMUGL_HOST/host/renderControl_dec/renderControl.in $EMUGL_HOST/host/renderControl_dec/renderControl.attrib Note that the EGL specification files are under a directory named "renderControl_dec" and have filenames that begin with "renderControl" This is mainly for historic reasons now, but is also related to the fact that this part of the wire protocol contains support functions/calls/specifications that are not part of the EGL specification itself, but add a few features required to make everything works. For example, they have calls related to the "gralloc" system library module used to manage graphics surfaces at a lower level than EGL. Generally speaking, guest encoder sources are located under directories named $EMUGL_GUEST/system/<name>_enc/, while the corresponding host decoder sources will be under $EMUGL_HOST/host/libs/<name>_dec/ However, all these sources use the same spec files located under the decoding directories. The encoder files are built from emugen and spec files located in $EMUGL_HOST and copied to the encoder directories in $EMUGL_GUEST by the gen-encoder.sh script. They are checked in, so that a given version of Android supports a specific version of the protocol, even if newer versions of the renderer (and future Android versions) support a newer protocol version. This step needs to be done manually when the protocol changes; these changes also need to be accompanied by changes in the renderer to handle the old version of the protocol. System libraries: ----------------- Meta EGL/GLES system libraries, and egl.cfg: - - - - - - - - - - - - - - - - - - - - - - It is important to understand that the emulation-specific EGL/GLES libraries are not directly linked by applications at runtime. Instead, the system provides a set of "meta" EGL/GLES libraries that will load the appropriate hardware-specific libraries on first use. More specifically, the system libEGL.so contains a "loader" which will try to load: - hardware-specific EGL/GLES libraries - the software-based rendering libraries (called "libagl") The system libEGL.so is also capable of merging the EGL configs of both the hardware and software libraries transparently to the application. The system libGLESv1_CM.so and libGLESv2.so, work with it to ensure that the thread's current context will be linked to either the hardware or software libraries depending on the config selected. For the record, the loader's source code in under frameworks/base/opengl/libs/EGL/Loader.cpp. It depends on a file named /system/lib/egl/egl.cfg which must contain two lines that look like: 0 1 <name> 0 0 android The first number in each line is a display number, and must be 0 since the system's EGL/GLES libraries don't support anything else. The second number must be 1 to indicate hardware libraries, and 0 to indicate a software one. The line corresponding to the hardware library, if any, must always appear before the one for the software library. The third field is a name corresponding to a shared library suffix. It really means that the corresponding libraries will be named libEGL_<name>.so, libGLESv1_CM_<name>.so and libGLESv2_<name>.so. Moreover these libraries must be placed under /system/lib/egl/ The name "android" is reserved for the system software renderer. The egl.cfg that comes with this project uses the name "emulation" for the hardware libraries. This means that it provides an egl.cfg file that contains the following lines: 0 1 emulation 0 0 android See $EMUGL_GUEST/system/egl/egl.cfg and more generally the following build files: $EMUGL_GUEST/system/egl/Android.mk $EMUGL_GUEST/system/GLESv1/Android.mk $EMUGL_GUEST/system/GLESv2/Android.mk to see how the libraries are named and placed under /system/lib/egl/ by the build system. Emulation libraries: - - - - - - - - - - - The emulator-specific libraries are under the following: $EMUGL_GUEST/system/egl/ $EMUGL_GUEST/system/GLESv1/ $EMUGL_GUEST/system/GLESv2/ The code for GLESv1 and GLESv2 is pretty small, since it mostly link against the static encoding libraries. The code for EGL is a bit more complex, because it needs to deal with extensions dynamically. I.e. if an extension is not available on the host it shouldn't be exposed by the library at runtime. So the EGL code queries the host for the list of available extensions in order to return them to clients. Similarly, it must query the list of valid EGLConfigs for the current host system. "gralloc" module implementation: - - - - - - - - - - - - - - - - - In addition to EGL/GLES libraries, the Android system requires a hardware-specific library to manage graphics surfaces at a level lower than EGL. This library must be what is called in Android land as a "HAL module". A "HAL module" must provide interfaces defined by Android's HAL (Hardware Abstraction Library). These interface definitions can be found under $ANDROID/hardware/libhardware/include/ Of all possible HAL modules, the "gralloc" one is used by the system's SurfaceFlinger to allocate framebuffers and other graphics memory regions, as well as eventually lock/unlock/swap them when needed. The code under $EMUGL/system/gralloc/ implements the module required by the GLES emulation project. It's not very long, but there are a few things to notice here: - first, it will probe the guest system to determine if the emulator that is running the virtual device really supports GPU emulation. In certain circumstances this may not be possible. If this is the case, then the module will redirect all calls to the "default" gralloc module that is normally used by the system when software-only rendering is enabled. The probing happens in the function "fallback_init" which gets called when the module is first opened. This initializes the 'sFallback' variable to a pointer to the default gralloc module when required. - second, this module is used by SurfaceFlinger to display "software surfaces", i.e. those that are backed by system memory pixel buffers, and written to directly through the Skia graphics library (i.e. the non-accelerated ones). the default module simply copies the pixel data from the surface to the virtual framebuffer i/o memory, but this project's gralloc module sends it to the renderer through the QEMU Pipe instead. It turns out that this results in _faster_ rendering/frame-rates overall, because memory copies inside the guest are slow, while QEMU pipe transfers are done directly in the emulator. Host Renderer: -------------- The host renderer library is located under $EMUGL_HOST/host/libs/libOpenglRender, and it provides an interface described by the headers under $EMUGL_HOST/host/include/libOpenglRender/render_api.h (e.g. for use by the emulator). In a nutshell, the rendering library is responsible for the following: - Providing a virtual off-screen video surface where everything will get rendered at runtime. Its dimensions are fixed by the call to initOpenglRender() that must happen just after the library is initialized. - Provide a way to display the virtual video surface on a host application's UI. This is done by calling createOpenGLSubWindow() which takes as argument the window ID or handle of a parent window, some display dimensions and a rotation angle. This allows the surface to be scaled/rotated when it is displayed, even if the dimensions of the video surface do not change. - Provide a way to listen to incoming EGL/GLES commands from the guest. This is done by providing a so-called "port number" to initOpenglRender(). By default, the port number corresponds to a local TCP port number that the renderer will bind to and listen. Every new connection to this port will correspond to the creation of a new guest host connection, each such connection corresponding to a distinct thread in the guest system. For performance reasons, it is possible to listen to either Unix sockets (on Linux and OS X), or to a Win32 named pipe (on Windows). To do so, one had to call setStreamType() between library initialization (i.e. initLibrary()) and construction (i.e. initOpenglRender()). Note that in these modes, the port number is still used to differentiate between several emulator instances. These details are normally handled by the emulator code so you shouldn't care too much. Note that an earlier version of the interface allowed a client of the renderer library to provide its own IOStream implementation. However, this wasn't very convenient for a number of reasons. This maybe something that could be done again if it makes sense, but for now the performance numbers are pretty good. Host emulator: -------------- The code under $QEMU/android/opengles.c is in charge of dynamically loading the rendering library and initializing / constructing it properly. QEMU pipe connections to the 'opengles' service are piped through the code in $QEMU/android/hw-pipe-net.c. Look for the openglesPipe_init() function, which is in charge of creating a connection to the renderer library (either through a TCP socket, or a Unix pipe depending on configuration. support for Win32 named pipes hasn't been implemented yet in the emulator) whenever a guest process opens the "opengles" service through /dev/qemu_pipe. There is also some support code for the display of the GLES framebuffer (through the renderer library's subwindow) under $QEMU/skin/window. Note that at the moment, scaling and rotation are supported. However, brightness emulation (which used to modify the pixel values from the hardware framebuffer before displaying them) doesn't work. Another issue is that it is not possible to display anything on top of the GL subwindow at the moment. E.g. this will obscure the emulated trackball image (that is normally toggled with Ctrl-T during emulation, or enabled by pressing the Delete key).