android emulator虚拟设备分析第五篇之pipe上的opengles

简介: 一、概述 据说qemu的gpu的实现,运行起来非常慢。所以android emulator提供了一种use host gpu的方式,guest os可以使用host机器的opengl库去画图,速度快很多。

一、概述

据说qemu的gpu的实现,运行起来非常慢。所以android emulator提供了一种use host gpu的方式,guest os可以使用host机器的opengl库去画图,速度快很多。

guest os把画图的命令通过pipe传递给emulator(encode, send via pipe, decode),然后emulator将opengles的画图命令转为opengl的画图命令(translate),并执行。


二、opengles —— pipe上的另一个service

老规矩,看文档,opengles是使用tcp实现的,tcp也是pipe service

ANDROID-QEMU-PIPE.TXT:306

opengles

   Connects to the OpenGL ES emulation process. For now, the implementation
   is equivalent to tcp:22468, but this may change in the future.


代码是hw-pipe-net.c,需要先看第二篇pipe的实现

初始化代码如下,初始化了opengles,tcp和unix三种

static const GoldfishPipeFuncs  openglesPipe_funcs = {
    openglesPipe_init,
    netPipe_closeFromGuest,
    netPipe_sendBuffers,
    netPipe_recvBuffers,
    netPipe_poll,
    netPipe_wakeOn,
    NULL,  /* we can't save these */
    NULL,  /* we can't load these */
};

void
android_net_pipes_init(void)
{
    Looper*  looper = looper_newCore();

    goldfish_pipe_add_type( "tcp", looper, &netPipeTcp_funcs );
#ifndef _WIN32
    goldfish_pipe_add_type( "unix", looper, &netPipeUnix_funcs );
#endif
    goldfish_pipe_add_type( "opengles", looper, &openglesPipe_funcs );
}

int
android_init_opengles_pipes(void)
{
    /* TODO: Check that we can load and initialize the host emulation
     *        libraries, and return -1 in case of error.
     */
    _opengles_init = 1;
    return 0;
}


openglesPipe_init在guest往/dev/qemu_pipe里面写"opengles"后,由pipeConnector_sendBuffers函数调用,返回一个NetPipe,和CHANNEL一一对应的

netPipe_initUnix or netPipe_initTcp建立了opengles pipe service和emulator中画图服务端的socket连接

static void*
openglesPipe_init( void* hwpipe, void* _looper, const char* args )
{
    NetPipe *pipe;

    if (!_opengles_init) {
        /* This should never happen, unless there is a bug in the
         * emulator's initialization, or the system image. */
        D("Trying to open the OpenGLES pipe without GPU emulation!");
        return NULL;
    }

    char server_addr[PATH_MAX];
    android_gles_server_path(server_addr, sizeof(server_addr));
#ifndef _WIN32
    if (android_gles_fast_pipes) {
        pipe = (NetPipe *)netPipe_initUnix(hwpipe, _looper, server_addr);
        D("Creating Unix OpenGLES pipe for GPU emulation: %s", server_addr);
    } else {
#else /* _WIN32 */
    {
#endif
        /* Connect through TCP as a fallback */
        pipe = (NetPipe *)netPipe_initTcp(hwpipe, _looper, server_addr);
        D("Creating TCP OpenGLES pipe for GPU emulation!");
    }
    if (pipe != NULL) {
        // Disable TCP nagle algorithm to improve throughput of small packets
        socket_set_nodelay(pipe->io->fd);

    // On Win32, adjust buffer sizes
#ifdef _WIN32
        {
            int sndbuf = 128 * 1024;
            int len = sizeof(sndbuf);
            if (setsockopt(pipe->io->fd, SOL_SOCKET, SO_SNDBUF,
                        (char*)&sndbuf, len) == SOCKET_ERROR) {
                D("Failed to set SO_SNDBUF to %d error=0x%x\n",
                sndbuf, WSAGetLastError());
            }
        }
#endif /* _WIN32 */
    }

    return pipe;
}


netPipe_initUnix or netPipe_initTcp都会调用到netPipe_initFromAddress函数,里面的loopIo_init和asyncConnector_init需要注意一下,大概意思是有个公用的循环,使用LoopIo把fd包装起来,循环里面会检查哪些fd可读,或者可写,并且调用callback函数netPipe_io_func

void*
netPipe_initFromAddress( void* hwpipe, const SockAddress*  address, Looper* looper )
{
    NetPipe*     pipe;

    ANEW0(pipe);

    pipe->hwpipe = hwpipe;
    pipe->state  = STATE_INIT;

    {
        AsyncStatus  status;

        int  fd = socket_create( sock_address_get_family(address), SOCKET_STREAM );
        if (fd < 0) {
            D("%s: Could create socket from address family!", __FUNCTION__);
            netPipe_free(pipe);
            return NULL;
        }

        loopIo_init(pipe->io, looper, fd, netPipe_io_func, pipe);
        status = asyncConnector_init(pipe->connector, address, pipe->io);
        pipe->state = STATE_CONNECTING;

        if (status == ASYNC_ERROR) {
            D("%s: Could not connect to socket: %s",
              __FUNCTION__, errno_str);
            netPipe_free(pipe);
            return NULL;
        }
        if (status == ASYNC_COMPLETE) {
            pipe->state = STATE_CONNECTED;
            netPipe_resetState(pipe);
        }
    }

    return pipe;
}


netPipe_io_func是刚才那个callback函数,就是用来唤醒等待读写的线程的,如果未连接,那么会先连接一下(connect)

/* This is the function that gets called each time there is an asynchronous
 * event on the network pipe.
 */
static void
netPipe_io_func( void* opaque, int fd, unsigned events )
{
    NetPipe*  pipe = opaque;
    int         wakeFlags = 0;

    /* Run the connector if we are in the CONNECTING state     */
    /* TODO: Add some sort of time-out, to deal with the case */
    /*        when the server is wedged.                      */
    if (pipe->state == STATE_CONNECTING) {
        AsyncStatus  status = asyncConnector_run(pipe->connector);
        if (status == ASYNC_NEED_MORE) {
            return;
        }
        else if (status == ASYNC_ERROR) {
            /* Could not connect, tell our client by closing the channel. */

            netPipe_closeFromSocket(pipe);
            return;
        }
        pipe->state = STATE_CONNECTED;
        netPipe_resetState(pipe);
        return;
    }

    /* Otherwise, accept incoming data */
    if ((events & LOOP_IO_READ) != 0) {
        if ((pipe->wakeWanted & PIPE_WAKE_READ) != 0) {
            wakeFlags |= PIPE_WAKE_READ;
        }
    }

    if ((events & LOOP_IO_WRITE) != 0) {
        if ((pipe->wakeWanted & PIPE_WAKE_WRITE) != 0) {
            wakeFlags |= PIPE_WAKE_WRITE;
        }
    }

    /* Send wake signal to the guest if needed */
    if (wakeFlags != 0) {
        goldfish_pipe_wake(pipe->hwpipe, wakeFlags);
        pipe->wakeWanted &= ~wakeFlags;
    }

    /* Reset state */
    netPipe_resetState(pipe);
}


通过/dev/qemu_pipe写东西,最终会调用到netPipe_sendBuffers函数(evil switch in pipeConnector_sendBuffers),然后通过刚才建立的socket发送给画图服务端

static int
netPipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers )
{
    NetPipe*  pipe = opaque;
    int       count = 0;
    int       ret   = 0;
    int       buffStart = 0;
    const GoldfishPipeBuffer* buff = buffers;
    const GoldfishPipeBuffer* buffEnd = buff + numBuffers;

    ret = netPipeReadySend(pipe);
    if (ret != 0)
        return ret;

    for (; buff < buffEnd; buff++)
        count += buff->size;

    buff = buffers;
    while (count > 0) {
        int  avail = buff->size - buffStart;
        int  len = socket_send(pipe->io->fd, buff->data + buffStart, avail);

        /* the write succeeded */
        if (len > 0) {
            buffStart += len;
            if (buffStart >= buff->size) {
                buff++;
                buffStart = 0;
            }
            count -= len;
            ret   += len;
            continue;
        }

        /* we reached the end of stream? */
        if (len == 0) {
            if (ret == 0)
                ret = PIPE_ERROR_IO;
            break;
        }

        /* if we already wrote some stuff, simply return */
        if (ret > 0) {
            break;
        }

        /* need to return an appropriate error code */
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            ret = PIPE_ERROR_AGAIN;
        } else {
            ret = PIPE_ERROR_IO;
        }
        break;
    }

    return ret;
}



从/dev/qemu_pipe读东西,最终会调用到netPipe_recvBuffers函数(evil switch in pipeConnector_sendBuffers),然后通过刚才建立的socket从画图服务端接收数据

static int
netPipe_recvBuffers( void* opaque, GoldfishPipeBuffer*  buffers, int  numBuffers )
{
    NetPipe*  pipe = opaque;
    int       count = 0;
    int       ret   = 0;
    int       buffStart = 0;
    GoldfishPipeBuffer* buff = buffers;
    GoldfishPipeBuffer* buffEnd = buff + numBuffers;

    for (; buff < buffEnd; buff++)
        count += buff->size;

    buff = buffers;
    while (count > 0) {
        int  avail = buff->size - buffStart;
        int  len = socket_recv(pipe->io->fd, buff->data + buffStart, avail);

        /* the read succeeded */
        if (len > 0) {
            buffStart += len;
            if (buffStart >= buff->size) {
                buff++;
                buffStart = 0;
            }
            count -= len;
            ret   += len;
            continue;
        }

        /* we reached the end of stream? */
        if (len == 0) {
            if (ret == 0)
                ret = PIPE_ERROR_IO;
            break;
        }

        /* if we already read some stuff, simply return */
        if (ret > 0) {
            break;
        }

        /* need to return an appropriate error code */
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            ret = PIPE_ERROR_AGAIN;
        } else {
            ret = PIPE_ERROR_IO;
        }
        break;
    }
    return ret;
}



netPipe_poll调用loopIo_poll判断是否POLLIN, POLLOUT

static unsigned
netPipe_poll( void* opaque )
{
    NetPipe*  pipe = opaque;
    unsigned  mask = loopIo_poll(pipe->io);
    unsigned  ret  = 0;

    if (mask & LOOP_IO_READ)
        ret |= PIPE_POLL_IN;
    if (mask & LOOP_IO_WRITE)
        ret |= PIPE_POLL_OUT;

    return ret;
}



netPipe_wakeOn设置想等待什么事件

static void
netPipe_wakeOn( void* opaque, int flags )
{
    NetPipe*  pipe = opaque;

    DD("%s: flags=%d", __FUNCTION__, flags);

    pipe->wakeWanted |= flags;
    netPipe_resetState(pipe);
}


三、使用host gpu

老规矩,先看文档,文档在模拟器的repo中可以找到,我在android的repo里面没找到

重点看external/qemu/distrib/android-emugl/DESIGN


libEGL.so、libGLESv1_CM.so和libGLESv2.so是纯软件方面的通用的代码,在模拟器上不需要任何特殊的处理

libEGL.so会根据egl.cfg的配置,dlopen几个库,在模拟器上是libEGL_emulation.so、libGLESv1_CM_emulation.so和libGLESv2_emulation.so,这几个库是硬件相关的

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


libEGL_emulation.so、libGLESv1_CM_emulation.so和libGLESv2_emulation.so对应了GUEST SYSTEM LIBRARIES,它们的函数最终都会通过<function_name>_enc进行encode,然后通过opengles pipe serice给到emulator,emulator再通过tcp socket发送给画图服务端,服务端的libOpenglRender.so使用几个decode的库去进行decode,然后分发给libEGL_translator.so、libGLES_CM_translator.so和libGLESv2_translator.so,它们将opengles的调用转为opengl的调用,然后使用host系统的opengl库去执行

         _________            __________          __________
        |         |          |          |        |          |
        |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
        |   GLX   |          |  GL 2.0  |        |  GL 2.0  |     LIBRARIES
        |_________|          |__________|        |__________|

    (NOTE: 'GLX' is for Linux only, replace 'AGL' on OS X, and 'WGL' on Windows).


qemu和ui是两个不同的进程,ui里面的是画图的服务端,qemu里面的是socket client,也就是opengles pipe service中建立的socket连接。


再看看external/qemu/docs/GPU-EMULATION.TXT

GPU emulation is controlled by the followind AVD hardware properties:

  hw.gpu.enabled    Boolean indicating whether GPU emulation is enabled.
                    If 'false', the builtin guest software renderer will be
                    used instead. Note that the latter is very slow and only
                    supports GLES 1.x, so most applications will not run with
                    it properly.

  hw.gpu.mode       Only used when hw.gpu.enabled is on. This is a string that
                    describes which emulation mode to support.

At this time, the following modes are supported:

  'host'    The default mode, uses specific translator libraries to convert
            guest EGL/GLES commands into host desktop GL ones. This requires
            valid OpenGL drivers installed on the development machine, and
            of course a display connection.

            Note that on some platforms (Windows in particular), OpenGL drivers
            can be buggy, resulting in poor performance, rendering artefacts
            or even crashes. To work-around this, use the 'mesa' mode instead.

  'mesa'    Software-based desktop GL renderer, based on the Mesa3D library.
            This is slower than 'host' mode by a large margin, but works
            equally well on all supported platforms. This is recommended if
            there are issues with 'host' mode.

            Another benefit of 'mesa' mode is that it can be run on headless
            servers which do not have a GPU, or OpenGL libraries installed.

  'auto'    Uses 'host' mode if not remoting (NX or CRD), 'mesa' otherwise.

  'guest'   Use a guest-side OpenGL ES implementation.





相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
2月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
208 4
|
2月前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
1月前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
32 8
|
3月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
94 15
Android 系统缓存扫描与清理方法分析
|
2月前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
44 1
|
2月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
1月前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
54 19
|
2月前
|
IDE Java 开发工具
移动应用与系统:探索Android开发之旅
在这篇文章中,我们将深入探讨Android开发的各个方面,从基础知识到高级技术。我们将通过代码示例和案例分析,帮助读者更好地理解和掌握Android开发。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。让我们一起开启Android开发的旅程吧!
|
1月前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
60 14
|
1月前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。