Android 显示系统:OpenGL简介和Gralloc代码分析
一、OpenGL ES与EGL
Android的GUI系统是基于OpenGL/EGL来实现的。
- 由于OpenGL是通用函数库,在不同平台系统上需要被“本土化”——把它与具体平台的窗口系统建立起关联,FramebufferNativeWindow是负责OpenGL ES在Android平台上本地化的中介之一。为OpenGL ES配置本地窗口的是EGL,EGL介于本地窗口系统和Rendering API(即OPenGL ES)之间的一层接口。
- 与OpenGL相关的模块可分为:
(1)配置类。 帮助OpenGL ES完成配置的,包括EGL、DisplayHardware都归为此类。
(2)依赖类。 OpenGL ES正常运行所依赖的“本地化”的东西,如FramebufferNativeWindow。
(3)使用类。 使用者也可能是配置者,如DisplayDevice既扮演了构建OpenGL ES环境的角色,同时也是它的用户。
二、终端显示设备 - Gralloc与Framebuffer
Linux内核提供了统一的framebuffer显示驱动。Framebuffer是内核系统提供的图形硬件的抽象描述,称为buffer是因为它也占用了系统存储空间的一部分,是一块包含屏幕显示信息的缓冲区。Framebuffer借助于Linux文件系统向上层应用提供了统一而高效的操作接口,让用户空间运行的程序比较容易地适配多种显示设备。
Android系统中,framebuffer提供的设备节点为/dev/graphics/fb或者/dev/fb,其中fb0表示第一个主显示屏幕,必须存在,当前系统实现中只用到了一个显示屏。显示系统使用HAL层间接引用底层架构,从而操作帧缓冲区。而完成这一中介任务的就是Gralloc.下图为Gralloc的模块简图,描述了gpu0和fb0的主要API.
Android.mk framebuffer.cpp gralloc.cpp gralloc_priv.h gr.h mapper.cpp
与其相关的头文件位于hardware/libhardware/include/hardware,涉及fb.h和gralloc.h。
以下从gralloc的调用开始分析代码(Android8.0):
FramebufferNativeWindow实现FrameBuffer的管理,它主要被SurfaceFlinger使用。也能够被OpenGL Native程序使用。在本质上,它在Framebuffer之上实现了一个ANativeWindow,眼下它仅仅管理两个buffers:front and back buffer。
/frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer_hwc1.cpp
HWComposer::HWComposer( const sp<SurfaceFlinger>& flinger, EventHandler& handler) : mFlinger(flinger), mFbDev(0), mHwc(0), mNumDisplays(1), mCBContext(new cb_context), mEventHandler(handler), mDebugForceFakeVSync(false) { for (size_t i =0 ; i<MAX_HWC_DISPLAYS ; i++) { mLists[i] = 0; } for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) { mLastHwVSync[i] = 0; mVSyncCounts[i] = 0; } char value[PROPERTY_VALUE_MAX]; property_get("debug.sf.no_hw_vsync", value, "0"); mDebugForceFakeVSync = atoi(value); bool needVSyncThread = true; // Note: some devices may insist that the FB HAL be opened before HWC. int fberr = loadFbHalModule(); loadHwcModule(); if (mFbDev && mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) { // close FB HAL if we don't needed it. // FIXME: this is temporary until we're not forced to open FB HAL // before HWC. framebuffer_close(mFbDev); mFbDev = NULL; } // If we have no HWC, or a pre-1.1 HWC, an FB dev is mandatory. if ((!mHwc || !hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) && !mFbDev) { ALOGE("ERROR: failed to open framebuffer (%s), aborting", strerror(-fberr)); abort(); } // these display IDs are always reserved for (size_t i=0 ; i<NUM_BUILTIN_DISPLAYS ; i++) { mAllocatedDisplayIDs.markBit(i); } if (mHwc) { ALOGI("Using %s version %u.%u", HWC_HARDWARE_COMPOSER, (hwcApiVersion(mHwc) >> 24) & 0xff, (hwcApiVersion(mHwc) >> 16) & 0xff); if (mHwc->registerProcs) { mCBContext->hwc = this; mCBContext->procs.invalidate = &hook_invalidate; mCBContext->procs.vsync = &hook_vsync; if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) mCBContext->procs.hotplug = &hook_hotplug; else mCBContext->procs.hotplug = NULL; memset(mCBContext->procs.zero, 0, sizeof(mCBContext->procs.zero)); mHwc->registerProcs(mHwc, &mCBContext->procs); } // don't need a vsync thread if we have a hardware composer needVSyncThread = false; // always turn vsync off when we start eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0); // the number of displays we actually have depends on the // hw composer version if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_3)) { // 1.3 adds support for virtual displays mNumDisplays = MAX_HWC_DISPLAYS; } else if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) { // 1.1 adds support for multiple displays mNumDisplays = NUM_BUILTIN_DISPLAYS; } else { mNumDisplays = 1; } } if (mFbDev) { ALOG_ASSERT(!(mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)), "should only have fbdev if no hwc or hwc is 1.0"); DisplayData& disp(mDisplayData[HWC_DISPLAY_PRIMARY]); disp.connected = true; disp.format = mFbDev->format; DisplayConfig config = DisplayConfig(); config.width = mFbDev->width; config.height = mFbDev->height; config.xdpi = mFbDev->xdpi; config.ydpi = mFbDev->ydpi; config.refresh = nsecs_t(1e9 / mFbDev->fps); disp.configs.push_back(config); disp.currentConfig = 0; } else if (mHwc) { // here we're guaranteed to have at least HWC 1.1 for (size_t i =0 ; i<NUM_BUILTIN_DISPLAYS ; i++) { queryDisplayProperties(i); } } if (needVSyncThread) { // we don't have VSYNC support, we need to fake it mVSyncThread = new VSyncThread(*this); } }
先依据gralloc的module ID来得到hw_module_t结构:
// Load and prepare the FB HAL, which uses the gralloc module. Sets mFbDev. int HWComposer::loadFbHalModule() { hw_module_t const* module; int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module); if (err != 0) { ALOGE("%s module not found", GRALLOC_HARDWARE_MODULE_ID); return err; } return framebuffer_open(module, &mFbDev); }
在hw_get_module里面调用hw_get_module_by_class,其中hw_module_exists会检测gralloc动态库的位置:
int hw_get_module_by_class(const char *class_id, const char *inst, const struct hw_module_t **module) { int i = 0; char prop[PATH_MAX] = {0}; char path[PATH_MAX] = {0}; char name[PATH_MAX] = {0}; char prop_name[PATH_MAX] = {0}; if (inst) snprintf(name, PATH_MAX, "%s.%s", class_id, inst); else strlcpy(name, class_id, PATH_MAX); /* * Here we rely on the fact that calling dlopen multiple times on * the same .so will simply increment a refcount (and not load * a new copy of the library). * We also assume that dlopen() is thread-safe. */ /* First try a property specific to the class and possibly instance */ snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name); if (property_get(prop_name, prop, NULL) > 0) { if (hw_module_exists(path, sizeof(path), name, prop) == 0) { goto found; } } /* Loop through the configuration variants looking for a module */ for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) { if (property_get(variant_keys[i], prop, NULL) == 0) { continue; } if (hw_module_exists(path, sizeof(path), name, prop) == 0) { goto found; } } /* Nothing found, try the default */ if (hw_module_exists(path, sizeof(path), name, "default") == 0) { goto found; } return -ENOENT; found: /* load the module, if this fails, we're doomed, and we should not try * to load a different variant. */ return load(class_id, path, module); }
找到gralloc库的路径后,会调用load函数使用dlopen打开找到的库,并依据HAL_MODULE_INFO_SYM_AS_STR(其值为HMI)获取到hw_module_t(即HAL_MODULE_INFO_SYM)结构体指针。以及把dlopen返回的handle保存在hw_module_t中,而hw_module_t HMI 结构是一个全局结构。
/** * Load the file defined by the variant and if successful * return the dlopen handle and the hmi. * @return 0 = success, !0 = failure. */ static int load(const char *id, const char *path, const struct hw_module_t **pHmi) { int status = -EINVAL; void *handle = NULL; struct hw_module_t *hmi = NULL; /* * load the symbols resolving undefined symbols before * dlopen returns. Since RTLD_GLOBAL is not or'd in with * RTLD_NOW the external symbols will not be global */ if (strncmp(path, "/system/", 8) == 0) { /* If the library is in system partition, no need to check * sphal namespace. Open it with dlopen. */ handle = dlopen(path, RTLD_NOW); } else { handle = android_load_sphal_library(path, RTLD_NOW); } if (handle == NULL) { char const *err_str = dlerror(); ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown"); status = -EINVAL; goto done; } /* Get the address of the struct hal_module_info. */ const char *sym = HAL_MODULE_INFO_SYM_AS_STR; hmi = (struct hw_module_t *)dlsym(handle, sym); if (hmi == NULL) { ALOGE("load: couldn't find symbol %s", sym); status = -EINVAL; goto done; } /* Check that the id matches */ if (strcmp(id, hmi->id) != 0) { ALOGE("load: id=%s != hmi->id=%s", id, hmi->id); status = -EINVAL; goto done; } hmi->dso = handle; /* success */ status = 0; done: if (status != 0) { hmi = NULL; if (handle != NULL) { dlclose(handle); handle = NULL; } } else { ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p", id, path, *pHmi, handle); } *pHmi = hmi; return status; }
回到HWComposer的构造函数处,接下来调用了loadFbHalModule() ---> framebuffer_open(module, &mFbDev); framebuffer_open定义在fb.h中,是一个inline函数:
/** convenience API for opening and closing a supported device */ static inline int framebuffer_open(const struct hw_module_t* module, struct framebuffer_device_t** device) { return module->methods->open(module, GRALLOC_HARDWARE_FB0, TO_HW_DEVICE_T_OPEN(device)); }
int gralloc_device_open(const hw_module_t* module, const char* name, hw_device_t** device) { int status = -EINVAL; if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) { gralloc_context_t *dev; dev = (gralloc_context_t*)malloc(sizeof(*dev)); /* initialize our state here */ memset(dev, 0, sizeof(*dev)); /* initialize the procs */ dev->device.common.tag = HARDWARE_DEVICE_TAG; dev->device.common.version = 0; dev->device.common.module = const_cast<hw_module_t*>(module); dev->device.common.close = gralloc_close; dev->device.alloc = gralloc_alloc; dev->device.free = gralloc_free; *device = &dev->device.common; status = 0; } else { status = fb_device_open(module, name, device); } return status; }
gralloc_device_open函数既可以打开fb设备也可用来打开gpu设备,如果传入的设备名为GRALLOC_HARDWARE_GPU0,即表示当前打开的是gpu设备。该函数主要是为了创建一个gralloc_context_t结构体,并且对它的成员变量device进行初始化,device变量的类型即为alloc_device_t,它用来描述一个gralloc设备,而gralloc设备是用来分配和释放图形缓冲区的,通过调用它成员函数alloc和free来实现。从这里可以看出,函数gralloc_device_open所打开的gralloc设备的成员函数alloc和free分别被设置为Gralloc模块中的函数gralloc_alloc和gralloc_free。
fb_device_open的定义在framebuffer.cpp中:
int fb_device_open(hw_module_t const* module, const char* name, hw_device_t** device) { int status = -EINVAL; if (!strcmp(name, GRALLOC_HARDWARE_FB0)) { /* initialize our state here */ fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev)); memset(dev, 0, sizeof(*dev)); /* initialize the procs */ dev->device.common.tag = HARDWARE_DEVICE_TAG; dev->device.common.version = 0; dev->device.common.module = const_cast<hw_module_t*>(module); dev->device.common.close = fb_close; dev->device.setSwapInterval = fb_setSwapInterval; dev->device.post = fb_post; dev->device.setUpdateRect = 0; private_module_t* m = (private_module_t*)module; status = mapFrameBuffer(m); if (status >= 0) { int stride = m->finfo.line_length / (m->info.bits_per_pixel >> 3); int format = (m->info.bits_per_pixel == 32) ? (m->info.red.offset ? HAL_PIXEL_FORMAT_BGRA_8888 : HAL_PIXEL_FORMAT_RGBX_8888) : HAL_PIXEL_FORMAT_RGB_565; const_cast<uint32_t&>(dev->device.flags) = 0; const_cast<uint32_t&>(dev->device.width) = m->info.xres; const_cast<uint32_t&>(dev->device.height) = m->info.yres; const_cast<int&>(dev->device.stride) = stride; const_cast<int&>(dev->device.format) = format; const_cast<float&>(dev->device.xdpi) = m->xdpi; const_cast<float&>(dev->device.ydpi) = m->ydpi; const_cast<float&>(dev->device.fps) = m->fps; const_cast<int&>(dev->device.minSwapInterval) = 1; const_cast<int&>(dev->device.maxSwapInterval) = 1; *device = &dev->device.common; } } return status; }
接下来的gralloc_open也是调用了gralloc_device_open,仅仅只是name參数一个是GRALLOC_HARDWARE_GPU0,而另外一个是GRALLOC_HARDWARE_FB0,这两个函数分别得到alloc_device_t 和 framebuffer_device_t结构。
到此,gralloc模块的三个主要结构体,gralloc_module_t,alloc_device_t,framebuffer_device_t都已经获取到了。当中在fb_device_open函数中会获取实际的framebuffer设备(一般是/dev/graphics/fb0)的一些重要參数以及能力,比如分辨率信息以及支持多少个缓冲等,另外会调用mapFrameBuffer--->mapFrameBufferLocked把framebuffer映射到内存的地址保存到gralloc_module_t中。android一般使用的都是双缓冲机制。详细代码例如以下:
int mapFrameBufferLocked(struct private_module_t* module) { // already initialized... if (module->framebuffer) { return 0; } char const * const device_template[] = { "/dev/graphics/fb%u", "/dev/fb%u", 0 }; int fd = -1; int i=0; char name[64]; while ((fd==-1) && device_template[i]) { snprintf(name, 64, device_template[i], 0); fd = open(name, O_RDWR, 0); i++; } if (fd < 0) return -errno; struct fb_fix_screeninfo finfo; if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) return -errno; struct fb_var_screeninfo info; if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1) return -errno; info.reserved[0] = 0; info.reserved[1] = 0; info.reserved[2] = 0; info.xoffset = 0; info.yoffset = 0; info.activate = FB_ACTIVATE_NOW; /* * Request NUM_BUFFERS screens (at lest 2 for page flipping) */ info.yres_virtual = info.yres * NUM_BUFFERS; uint32_t flags = PAGE_FLIP; #if USE_PAN_DISPLAY if (ioctl(fd, FBIOPAN_DISPLAY, &info) == -1) { ALOGW("FBIOPAN_DISPLAY failed, page flipping not supported"); #else if (ioctl(fd, FBIOPUT_VSCREENINFO, &info) == -1) { ALOGW("FBIOPUT_VSCREENINFO failed, page flipping not supported"); #endif info.yres_virtual = info.yres; flags &= ~PAGE_FLIP; } if (info.yres_virtual < info.yres * 2) { // we need at least 2 for page-flipping info.yres_virtual = info.yres; flags &= ~PAGE_FLIP; ALOGW("page flipping not supported (yres_virtual=%d, requested=%d)", info.yres_virtual, info.yres*2); } if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1) return -errno; uint64_t refreshQuotient = ( uint64_t( info.upper_margin + info.lower_margin + info.yres ) * ( info.left_margin + info.right_margin + info.xres ) * info.pixclock ); /* Beware, info.pixclock might be 0 under emulation, so avoid a * division-by-0 here (SIGFPE on ARM) */ int refreshRate = refreshQuotient > 0 ? (int)(1000000000000000LLU / refreshQuotient) : 0; if (refreshRate == 0) { // bleagh, bad info from the driver refreshRate = 60*1000; // 60 Hz } if (int(info.width) <= 0 || int(info.height) <= 0) { // the driver doesn't return that information // default to 160 dpi info.width = ((info.xres * 25.4f)/160.0f + 0.5f); info.height = ((info.yres * 25.4f)/160.0f + 0.5f); } float xdpi = (info.xres * 25.4f) / info.width; float ydpi = (info.yres * 25.4f) / info.height; float fps = refreshRate / 1000.0f; ALOGI( "using (fd=%d)\n" "id = %s\n" "xres = %d px\n" "yres = %d px\n" "xres_virtual = %d px\n" "yres_virtual = %d px\n" "bpp = %d\n" "r = %2u:%u\n" "g = %2u:%u\n" "b = %2u:%u\n", fd, finfo.id, info.xres, info.yres, info.xres_virtual, info.yres_virtual, info.bits_per_pixel, info.red.offset, info.red.length, info.green.offset, info.green.length, info.blue.offset, info.blue.length ); ALOGI( "width = %d mm (%f dpi)\n" "height = %d mm (%f dpi)\n" "refresh rate = %.2f Hz\n", info.width, xdpi, info.height, ydpi, fps ); if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) return -errno; if (finfo.smem_len <= 0) return -errno; module->flags = flags; module->info = info; module->finfo = finfo; module->xdpi = xdpi; module->ydpi = ydpi; module->fps = fps; /* * map the framebuffer */ int err; size_t fbSize = roundUpToPageSize(finfo.line_length * info.yres_virtual); module->framebuffer = new private_handle_t(dup(fd), fbSize, 0); module->numBuffers = info.yres_virtual / info.yres; module->bufferMask = 0; void* vaddr = mmap(0, fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (vaddr == MAP_FAILED) { ALOGE("Error mapping the framebuffer (%s)", strerror(errno)); return -errno; } module->framebuffer->base = intptr_t(vaddr); memset(vaddr, 0, fbSize); return 0; }
由上面函数看出。mapFrameBufferLocked主要做了以下几件事情:
- 打开framebuffer设备
- 获取 fb_fix_screeninfo and fb_var_screeninfo
- refill fb_var_screeninfo
- 推断是否支持PAGE_FLIP
- 计算刷新率
- 打印gralloc信息
- 填充private_module_t
- mmap the framebuffer
typedef struct framebuffer_device_t { /** * Common methods of the framebuffer device. This *must* be the first member of * framebuffer_device_t as users of this structure will cast a hw_device_t to * framebuffer_device_t pointer in contexts where it's known the hw_device_t references a * framebuffer_device_t. */ struct hw_device_t common; /* flags describing some attributes of the framebuffer */ const uint32_t flags; /* dimensions of the framebuffer in pixels */ const uint32_t width; const uint32_t height; /* frambuffer stride in pixels */ const int stride; /* framebuffer pixel format */ const int format; /* resolution of the framebuffer's display panel in pixel per inch*/ const float xdpi; const float ydpi; /* framebuffer's display panel refresh rate in frames per second */ const float fps; /* min swap interval supported by this framebuffer */ const int minSwapInterval; /* max swap interval supported by this framebuffer */ const int maxSwapInterval; /* Number of framebuffers supported*/ const int numFramebuffers; int reserved[7]; /* * requests a specific swap-interval (same definition than EGL) * * Returns 0 on success or -errno on error. */ int (*setSwapInterval)(struct framebuffer_device_t* window, int interval); /* * This hook is OPTIONAL. * * It is non NULL If the framebuffer driver supports "update-on-demand" * and the given rectangle is the area of the screen that gets * updated during (*post)(). * * This is useful on devices that are able to DMA only a portion of * the screen to the display panel, upon demand -- as opposed to * constantly refreshing the panel 60 times per second, for instance. * * Only the area defined by this rectangle is guaranteed to be valid, that * is, the driver is not allowed to post anything outside of this * rectangle. * * The rectangle evaluated during (*post)() and specifies which area * of the buffer passed in (*post)() shall to be posted. * * return -EINVAL if width or height <=0, or if left or top < 0 */ int (*setUpdateRect)(struct framebuffer_device_t* window, int left, int top, int width, int height); /* * Post <buffer> to the display (display it on the screen) * The buffer must have been allocated with the * GRALLOC_USAGE_HW_FB usage flag. * buffer must be the same width and height as the display and must NOT * be locked. * * The buffer is shown during the next VSYNC. * * If the same buffer is posted again (possibly after some other buffer), * post() will block until the the first post is completed. * * Internally, post() is expected to lock the buffer so that a * subsequent call to gralloc_module_t::(*lock)() with USAGE_RENDER or * USAGE_*_WRITE will block until it is safe; that is typically once this * buffer is shown and another buffer has been posted. * * Returns 0 on success or -errno on error. */ int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer); /* * The (*compositionComplete)() method must be called after the * compositor has finished issuing GL commands for client buffers. */ int (*compositionComplete)(struct framebuffer_device_t* dev); /* * This hook is OPTIONAL. * * If non NULL it will be caused by SurfaceFlinger on dumpsys */ void (*dump)(struct framebuffer_device_t* dev, char *buff, int buff_len); /* * (*enableScreen)() is used to either blank (enable=0) or * unblank (enable=1) the screen this framebuffer is attached to. * * Returns 0 on success or -errno on error. */ int (*enableScreen)(struct framebuffer_device_t* dev, int enable); void* reserved_proc[6]; } framebuffer_device_t; /** convenience API for opening and closing a supported device */ static inline int framebuffer_open(const struct hw_module_t* module, struct framebuffer_device_t** device) { return module->methods->open(module, GRALLOC_HARDWARE_FB0, TO_HW_DEVICE_T_OPEN(device)); } static inline int framebuffer_close(struct framebuffer_device_t* device) { return device->common.close(&device->common); } __END_DECLS #endif // ANDROID_FB_INTERFACE_H
另外,里面定义了一些操作framebuffer的函数:
1. int (*setSwapInterval)(struct framebuffer_device_t* window, int interval); // 这个函数基本没实用,由于maxSwapInterval=minSwapInterval= 1。 2. int (*setUpdateRect)(struct framebuffer_device_t* window, int left, int top, int width, int height); // 局部刷新用的,默认没有启用,和平台有关。 3. int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer); // 这个是最关键的函数,用来将图形缓冲区buffer的内容渲染到帧缓冲区中去,即显示在设备的显示屏中去。
具体看下post函数的实现:
static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer) { if (private_handle_t::validate(buffer) < 0) return -EINVAL; fb_context_t* ctx = (fb_context_t*)dev; private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(buffer); private_module_t* m = reinterpret_cast<private_module_t*>( dev->common.module); if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) { const size_t offset = hnd->base - m->framebuffer->base; m->info.activate = FB_ACTIVATE_VBL; m->info.yoffset = offset / m->finfo.line_length; if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) { ALOGE("FBIOPUT_VSCREENINFO failed"); m->base.unlock(&m->base, buffer); return -errno; } m->currentBuffer = buffer; } else { // If we can't do the page_flip, just copy the buffer to the front // FIXME: use copybit HAL instead of memcpy void* fb_vaddr; void* buffer_vaddr; m->base.lock(&m->base, m->framebuffer, GRALLOC_USAGE_SW_WRITE_RARELY, 0, 0, m->info.xres, m->info.yres, &fb_vaddr); m->base.lock(&m->base, buffer, GRALLOC_USAGE_SW_READ_RARELY, 0, 0, m->info.xres, m->info.yres, &buffer_vaddr); memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres); m->base.unlock(&m->base, buffer); m->base.unlock(&m->base, m->framebuffer); } return 0; }
从fb_post的函数定义能够看出,第一种方式是当private_handle_t结构体hnd所描写叙述的图形缓冲区是在系统帧缓冲区中分配的时候,即这个图形缓冲区的标志值flags的PRIV_FLAGS_FRAMEBUFFER位等于1的时候,是不须要将图形缓冲区的内容复制到系统帧缓冲区去的,由于将内容写入到图形缓冲区的时候,已经相当于是将内容写入到了系统帧缓冲区中去了把Framebuffer的后buffer切为前buffer,然后通过IOCTRL机制告诉FB驱动切换DMA源地地址即可。设置系统帧缓冲区的当前输出图形缓冲区是通过IO控制命令FBIOPUT_VSCREENINFO来进行的,需要一个fb_var_screeninfo结构体作为参数。
另外一种方式是利用copy的方式来实现,当private_handle_t结构体hnd所描写叙述的图形缓冲区是在内存中分配时,即这个图形缓冲区的标志值flags的PRIV_FLAGS_FRAMEBUFFER位等于0的时候,我们就须要将它的内容复制到系统帧缓冲区中去,这个拷贝的工作是通过调用函数memcpy来完成,相比效率较低。在将一块内存缓冲区的内容复制到系统帧缓冲区中去之前,须要对这两块缓冲区进行锁定,以保证在拷贝的过程中,这两块缓冲区的内容不会被改动,这个锁定的工作是由Gralloc模块中的函数gralloc_lock来实现的。Gralloc模块中的函数gralloc_lock的地址正好就保存在private_module_t结构体m的成员变量base所描写叙述的一个gralloc_module_t结构体的成员函数lock中。将要渲染的图形缓冲区的内容复制到系统帧缓冲区之后,就能够通过Gralloc模块中的函数gralloc_unlock来解锁。
以下再看看alloc_device_t的内容:
typedef struct alloc_device_t { struct hw_device_t common; /* * (*alloc)() Allocates a buffer in graphic memory with the requested * parameters and returns a buffer_handle_t and the stride in pixels to * allow the implementation to satisfy hardware constraints on the width * of a pixmap (eg: it may have to be multiple of 8 pixels). * The CALLER TAKES OWNERSHIP of the buffer_handle_t. * * If format is HAL_PIXEL_FORMAT_YCbCr_420_888, the returned stride must be * 0, since the actual strides are available from the android_ycbcr * structure. * * Returns 0 on success or -errno on error. */ int (*alloc)(struct alloc_device_t* dev, int w, int h, int format, int usage, buffer_handle_t* handle, int* stride); /* * (*free)() Frees a previously allocated buffer. * Behavior is undefined if the buffer is still mapped in any process, * but shall not result in termination of the program or security breaches * (allowing a process to get access to another process' buffers). * THIS FUNCTION TAKES OWNERSHIP of the buffer_handle_t which becomes * invalid after the call. * * Returns 0 on success or -errno on error. */ int (*free)(struct alloc_device_t* dev, buffer_handle_t handle); /* This hook is OPTIONAL. * * If non NULL it will be caused by SurfaceFlinger on dumpsys */ void (*dump)(struct alloc_device_t *dev, char *buff, int buff_len); void* reserved_proc[7]; } alloc_device_t;
从其结构体成员能够看出,其主要作用是为请求者分配图形缓冲区。先看alloc函数的实现(gralloc.cpp):
static int gralloc_alloc(alloc_device_t* dev, int width, int height, int format, int usage, buffer_handle_t* pHandle, int* pStride) { if (!pHandle || !pStride) return -EINVAL; int bytesPerPixel = 0; switch (format) { case HAL_PIXEL_FORMAT_RGBA_FP16: bytesPerPixel = 8; break; case HAL_PIXEL_FORMAT_RGBA_8888: case HAL_PIXEL_FORMAT_RGBX_8888: case HAL_PIXEL_FORMAT_BGRA_8888: bytesPerPixel = 4; break; case HAL_PIXEL_FORMAT_RGB_888: bytesPerPixel = 3; break; case HAL_PIXEL_FORMAT_RGB_565: case HAL_PIXEL_FORMAT_RAW16: bytesPerPixel = 2; break; default: return -EINVAL; } const size_t tileWidth = 2; const size_t tileHeight = 2; size_t stride = align(width, tileWidth); size_t size = align(height, tileHeight) * stride * bytesPerPixel + 4; int err; if (usage & GRALLOC_USAGE_HW_FB) { err = gralloc_alloc_framebuffer(dev, size, usage, pHandle); } else { err = gralloc_alloc_buffer(dev, size, usage, pHandle); } if (err < 0) { return err; } *pStride = stride; return 0; }
参数w表示要分配的图形缓冲区所保存的图像的宽度,将它乘以bpp,就能够得到保存一行像素所须要使用的字节数。需要将这个字节数对齐到4个字节边界,最后得到一行像素所须要的字节数就保存在变量bpr中。
参数h表示要分配的图形缓冲区所保存的图像的高度,将它乘以bpr。就能够得到保存整个图像所须要使用的字节数。将变量bpr的值除以变量bpp的值,就得到要分配的图形缓冲区一行包括有多少个像素点,这个结果须要保存在输出參数pStride中,以便能够返回给调用者。
参数format用来描写叙述要分配的图形缓冲区的颜色格式,描写叙述一个像素须要几个字节来表示。
参数usage用来描写叙述要分配的图形缓冲区的用途,假设是用来在系统帧缓冲区中渲染的,即参数usage的GRALLOC_USAGE_HW_FB位等于1,那么就必须在系统帧缓冲区中分配,否则的话,就在内存中分配。注意,在内存中分配的图形缓冲区,最终是需要复制到系统帧缓冲区去的,以便能够将它所描写叙述的图形渲染出来。
函数gralloc_alloc_framebuffer用来在系统帧缓冲区中分配图形缓冲区,而函数gralloc_alloc_buffer用来在内存在分配图形缓冲区,接下来就来看看这两个函数的实现。
gralloc_alloc_framebuffer最终调用了gralloc_alloc_framebuffer_locked(gralloc.cpp):
static int gralloc_alloc_framebuffer_locked(alloc_device_t* dev, size_t size, int usage, buffer_handle_t* pHandle) { private_module_t* m = reinterpret_cast<private_module_t*>( dev->common.module); // allocate the framebuffer if (m->framebuffer == NULL) { // initialize the framebuffer, the framebuffer is mapped once // and forever. int err = mapFrameBufferLocked(m); if (err < 0) { return err; } } const uint32_t bufferMask = m->bufferMask; const uint32_t numBuffers = m->numBuffers; const size_t bufferSize = m->finfo.line_length * m->info.yres; if (numBuffers == 1) { // If we have only one buffer, we never use page-flipping. Instead, // we return a regular buffer which will be memcpy'ed to the main // screen when post is called. int newUsage = (usage & ~GRALLOC_USAGE_HW_FB) | GRALLOC_USAGE_HW_2D; return gralloc_alloc_buffer(dev, bufferSize, newUsage, pHandle); } if (bufferMask >= ((1LU<<numBuffers)-1)) { // We ran out of buffers. return -ENOMEM; } // create a "fake" handles for it intptr_t vaddr = intptr_t(m->framebuffer->base); private_handle_t* hnd = new private_handle_t(dup(m->framebuffer->fd), size, private_handle_t::PRIV_FLAGS_FRAMEBUFFER); // find a free slot for (uint32_t i=0 ; i<numBuffers ; i++) { if ((bufferMask & (1LU<<i)) == 0) { m->bufferMask |= (1LU<<i); break; } vaddr += bufferSize; } hnd->base = vaddr; hnd->offset = vaddr - intptr_t(m->framebuffer->base); *pHandle = hnd; return 0; }
bufferMask用来描述系统帧缓冲区的使用情况,而变量numBuffers用来描述系统帧缓冲区可以划分为多少个图形缓冲区。另外一个变量bufferSize用来描述设备显示屏一屏内容所占用的内存的大小。假设系统帧缓冲区仅仅有一个图形缓冲区大小,即变量numBuffers的值等于1,那么这个图形缓冲区就始终用作系统主图形缓冲区来使用,在这样的情况下,就不可以在系统帧缓冲区中分配图形缓冲区来给用户空间的应用程序使用,因此,这时候就会转向内存中来分配图形缓冲区,即调用函数gralloc_alloc_buffer来分配图形缓冲区。注意,这时候分配的图形缓冲区的大小为一屏内容的大小,即bufferSize。
假设bufferMask的值大于等于((1LU<<numBuffers)-1)的值,就说明系统帧缓冲区中的图形缓冲区所有都分配出去了,这时候分配图形缓冲区就失败了。比如,假设图形缓冲区的个数为2,那么((1LU<<numBuffers)-1)的值就等于3,即二制制0x11,假设此时bufferMask的值也等于0x11,那么就表示第一个和第二个图形缓冲区都已经分配出去了,此时就不能再在系统帧缓冲区中分配图形缓冲区了。如果此时系统帧缓冲区中尚有空暇的图形缓冲区的,接下来函数就会创建一个private_handle_t结构体hnd来描写叙述这个即将要分配出去的图形缓冲区,注意,这个图形缓冲区的标志值为PRIV_FLAGS_FRAMEBUFFER,即表示这是一块在系统帧缓冲区中分配的图形缓冲区。接下来的for循环从低位到高位检查变量bufferMask的值,而且找到第一个值等于0的位,这样就能够知道在系统帧缓冲区中第几个图形缓冲区的是空暇的。注意,变量vadrr的值开始的时候指向系统帧缓冲区的基地址,在以下的for循环中,每循环一次它的值都会添加bufferSize。
以上可见每次从系统帧缓冲区中分配出去的图形缓冲区的大小都是刚好等于显示屏一屏内容大小,最后分配出去的图形缓冲区的起始地址就保存在前面所创建的private_handle_t结构体hnd成员变量base中,用户空间的应用程序就能够直接将要渲染的图形内容复制到这个地址上去,这就相当于是直接将图形渲染到系统帧缓冲区中去了。在将private_handle_t结构体hnd返回给调用者之前,还须要设置它的成员变量offset,以便能够知道它所描写叙述的图形缓冲区的起始地址相对于系统帧缓冲区基地址的偏移量。
gralloc_alloc_buffer(gralloc.cpp)的实现如下:
static int gralloc_alloc_buffer(alloc_device_t* dev, size_t size, int /*usage*/, buffer_handle_t* pHandle) { int err = 0; int fd = -1; size = roundUpToPageSize(size); fd = ashmem_create_region("gralloc-buffer", size); if (fd < 0) { ALOGE("couldn't create ashmem (%s)", strerror(-errno)); err = -errno; } if (err == 0) { private_handle_t* hnd = new private_handle_t(fd, size, 0); gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>( dev->common.module); err = mapBuffer(module, hnd); if (err == 0) { *pHandle = hnd; } } ALOGE_IF(err, "gralloc failed err=%s", strerror(-err)); return err; }
它首先调用函数ashmem_create_region来创建一块匿名共享内存,接着再在这块匿名共享内存上分配一个图形缓冲区。注意,这个图形缓冲区也是使用一个private_handle_t结构体来描述的,只是这个图形缓冲区的标志值等于0,以区别于在系统帧缓冲区中分配的图形缓冲区。当中mapBuffer又把hnd所描写叙述的一个图形缓冲区映射到当前进程的地址空间来。
以上内容就是alloc_device_t的相关内容。在private_module_t中有一个registerBuffer的函数指针,此函数是干什么的呢?在Android系统中,全部的图形缓冲区都是由SurfaceFlinger服务分配的,而当一个图形缓冲区被分配的时候,它会同一时候被映射到请求分配的进程的地址空间去,即分配过程同时包含了注册过程,但对用户空间的其他的应用程序来说,它们所须要的图形缓冲区是在由SurfaceFlinger服务分配的,因此,当它们得到SurfaceFlinger服务分配的图形缓冲区之后,还须要将这块图形缓冲区映射到自己的地址空间来,以便能够使用这块图形缓冲区。
在系统帧缓冲区中分配的图形缓冲区仅仅在SurfaceFlinger服务中使用,而SurfaceFlinger服务在初始化系统帧缓冲区的时候,已经将系统帧缓冲区映射到自己所在的进程中来了,因此,函数gralloc_map假设发现要注册的图形缓冲区是在系统帧缓冲区分配的时候,那么就无需再运行映射图形缓冲区的操作了。假设要注册的图形缓冲区是在内存中分配的,即它的标志值flags的PRIV_FLAGS_FRAMEBUFFER位等于0,那么接下来就需要将它映射到当前进程的地址空间来,因为要注册的图形缓冲区是在文件描述符hnd->fd对应的一块匿名共享内存中分配的,因此,仅仅需要将文件描述符hnd->fd对应的匿名共享内存映射到当前进程的地址空间来。因为映射文件描述符hnd->fd得到的是一整块匿名共享内存在当前进程地址空间的基地址,而要注册的图形缓冲区可能仅仅占领这块匿名共享内存的某一小部分,因此,还需要将要注册的图形缓冲区的在被映射的匿名共享内存中的偏移量hnd->offset加上被映射的匿名共享内存的基地址hnd->base,才能够得到真正要注册的图形缓冲区在当前进程中的访问地址,这个地址最终要被写到hnd->base中去。
posted on 2019-07-31 18:55 sheldon_blogs 阅读(6770) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库