首页 文章 精选 留言 我的

精选列表

搜索[Pixel设备],共10000篇文章
优秀的个人博客,低调大师

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

一、概述 据说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 ornetPipe_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 ornetPipe_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 inpipeConnector_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.

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册