Gralloc is the HAL layer module in Android that is responsible for applying and releasing graphicBuffers. It is implemented by hardware drivers and provides the basis for BufferQueue mechanism. The graphic Buffer allocated by Gralloc is shared between processes and supports reads and writes on different hardware devices according to its Flag.

At the system level,grallocHAL layer module belongs to the lowest layer and is the upper layerlibuiThe library provides services, and the overall hierarchy is as follows:

  • The bottom isgrallocHAL module.
  • Up islibuiA library whose main function is to encapsulate pairsgrallocHAL layer call. The code directory isframeworks/native/include/uiandframeworks/native/libs/ui.
  • Is uplibguiLibrary, whose main function is to encapsulate the connection between SF client and serverBufferQueueAnd down depends on philibui. The code directory isframeworks/native/include/guiandframeworks/native/libs/gui.
  • At the top is the user,Skia,HwuiandOpenGL ESisBufferQueueThe manufacturer of,SurfaceFlingerisBufferQueueThe consumer of.

This article focuses on gralloc and LiBUI layers.

gralloc

The grallocHAL module structure is defined in gralloc. H:

// The module ID of gralloc
#define GRALLOC_HARDWARE_MODULE_ID "gralloc"
// Gralloc device ID
#define GRALLOC_HARDWARE_GPU0 "gpu0"

// Gralloc extends the HAL layer module structure
typedef struct gralloc_module_t {
    Hw_module_t represents a generic hardware module, which is the soul of HAL layer and inherits hw_module_T
    struct hw_module_t common;
    // When graphicBuffers allocated by other processes are passed to the current process, this method needs to be mapped to the current process to prepare for subsequent locks
    int (*registerBuffer)(struct gralloc_module_t const* module.buffer_handle_t handle);
    // Cancel GraphicBuffer mapping in the current process, can not call lock later
    int (*unregisterBuffer)(struct gralloc_module_t const* module.buffer_handle_t handle);
    If usage specifies GRALLOC_USAGE_SW_* flag, vaddr will be filled into the virtual memory address of the graph Buffer, and the user can directly write pixel data to this address
    int (*lock)(struct gralloc_module_t const* module.buffer_handle_t handle, int usage,
            int l, int t, int w, int h,
            void** vaddr);
    // After writing to the graph Buffer, unlock commits data
    int (*unlock)(struct gralloc_module_t const* module.buffer_handle_t handle);
            
    // Other functions......
} gralloc_module_t;

// Gralloc extends HAL layer device structure
typedef struct alloc_device_t {
    Hw_device_t indicates a common hardware device, which inherits the HW_device_t structure
    struct hw_device_t common;
    // Apply a graphic buffer and identify it with buffer_HANDLE_t
    int (*alloc)(struct alloc_device_t* dev,
            int w, int h, int format, int usage,
            buffer_handle_t* handle, int* stride);
    // Release a graphic buffer identified by buffer_HANDLE_t
    int (*free)(struct alloc_device_t* dev,
            buffer_handle_t handle);
            
    // Other functions......
}
Copy the code

As mentioned earlier in the HWC module, every HAL layer module implementation defines a HAL_MODULE_INFO_SYM data structure, and the first field of that structure must be hw_module_T. Gralloc. CPP provides the default implementation of gralloc, and the corresponding shared library is gralloc.default.so. Qualcomm MSM8994 also provides the implementation, the corresponding shared library is gralloc.mSM8994. So, the following is the definition of default:

struct private_module_t HAL_MODULE_INFO_SYM = {
    // base indicates the gralloc_module_T structure
    .base = {
        // common indicates the hw_module_T structure
        .common = {
            .tag = HARDWARE_MODULE_TAG,
            .version_major = 1,
            .version_minor = 0,
            .id = GRALLOC_HARDWARE_MODULE_ID,
            .name = "Graphics Memory Allocator Module",
            .author = "The Android Open Source Project".// Open the gralloc device alloc_device_t function
            .methods = &gralloc_module_methods
        },
        // Extended field for gralloc_module_t
        .registerBuffer = gralloc_register_buffer,
        .unregisterBuffer = gralloc_unregister_buffer,
        .lock = gralloc_lock,
        .unlock = gralloc_unlock,
    },
    .framebuffer = 0,
    .flags = 0,
    .numBuffers = 0,
    .bufferMask = 0,
    .lock = PTHREAD_MUTEX_INITIALIZER,
    .currentBuffer = 0};Copy the code

libui

Libui library mainly encapsulates the call to grallocHAL module, manages the allocation and release of GraphicBuffer and the mapping between different processes. It mainly contains three core classes, as shown in the following class diagram:

  • GraphicBuffer: the correspondinggrallocThe allocated graphic Buffer (or plain memory, depending on the gralloc implementation) inheritsANativeWindowBufferStructure, the core is a pointer to the graphics memory (buffer_handle_t), and the graphic Buffer itself is shared by multiple processes, and is transferred across processesGraphicBufferKey attributes so that the process can be reconstructed in useGraphicBufferAnd points to the same graphic Buffer.
  • GraphicBufferAllocator: Downward dockinggrallocHAL modulealloc_device_tThe device, which is an in-process singleton, is responsible for allocating graphics buffers shared between processes, externally i.eGraphicBuffer.
  • GraphicBufferMapper: Downward dockinggrallocHAL modulegralloc_module_tA module, which is an in-process singleton, is responsible for puttingGraphicBufferAllocatorThe distribution ofGraphicBufferMap to the current process space.

Next, let’s look at the timing logic for applying and releasing GraphicBuffer in the current process:

The attributes of the GraphicBuffer successfully applied will be saved in the corresponding field of the GraphicBuffer parent class ANativeWindowBuffer:

Stride * height * number of bytes per pixel
typedef struct ANativeWindowBuffer {
    // The width of the graph Buffer
    int width;
    // The height of the graph Buffer
    int height;
    // The step size of the graph Buffer, to handle alignment issues, may be different from height
    int stride;
    // The pixel format of the graphic Buffer
    int format;
    Gralloc allocates graphics buffers with different attributes.
    int usage;
    // Point to a graphic Buffer
    buffer_handle_t handle; 
} ANativeWindowBuffer_t;
Copy the code

Figure Buffer Size = Stride * height * number of bytes per pixel

In addition to applying a GraphicBuffer directly, you can also create a GraphicBuffer based on the different forms of the existing GraphicBuffer:

  • ANativeWindowBuffer
  • native_handle_t(i.e.buffer_handle_t)

The logic is simple, and you can refer directly to the GraphicBuffer overloaded constructor, which is not described here.

Above, ultimately through GraphicBufferAllocator. MAllocDev (alloc_device_t) distribution graphics Buffer, mAllocDev is initialized in GraphicBufferAllocator constructor:

// GraphicBufferAllocator is an in-process singleton
GraphicBufferAllocator::GraphicBufferAllocator(): mAllocDev(0)
{
    hw_module_t const* module; 
    // As with HWC, open the gralloc module
    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    if (err == 0) {
        // Open the gralloc device and save it in mAllocDev
        gralloc_open(module, &mAllocDev); }}Copy the code

As with turning on the HWC device, turning on the Gralloc module first and then the Gralloc device is a general process for manipulating the HAL module.

In addition to the width, height, and pixel formats, there is also a usage parameter, which represents the application behavior of GraphicBuffer. Gralloc can optimize the usage according to the usage. Gralloc defines the usage enumeration value:

  • GRALLOC_USAGE_SW_READ_NEVER: CPU does not read GraphicBuffer
  • GRALLOC_USAGE_SW_READ_RARELY: CPU rarely reads GraphicBuffer
  • GRALLOC_USAGE_SW_READ_OFTEN: The CPU reads GraphicBuffer frequently
  • GRALLOC_USAGE_SW_WRITE_NEVER: CPU will not write GraphicBuffer
  • GRALLOC_USAGE_SW_WRITE_RARELY writes GraphicBuffer
  • GRALLOC_USAGE_SW_WRITE_OFTEN: CPU often writes GraphicBuffer
  • GRALLOC_USAGE_HW_TEXTURE: GraphicBuffer can be uploaded as OpenGL ES texture, equivalent to GPU reading GraphicBuffer
  • GRALLOC_USAGE_HW_RENDER: GraphicBuffer can be used as a rendering target for OpenGL ES, equivalent to GPU writing GraphicBuffer
  • GRALLOC_USAGE_HW_2D: GraphicBuffer will be used by the 2D Hardware blitter
  • GRALLOC_USAGE_HW_COMPOSER: HWC can be synthesized directly using GraphicBuffer
  • GRALLOC_USAGE_HW_VIDEO_ENCODER: GraphicBuffer can be used as an input object for the Video hard encoder
  • GRALLOC_USAGE_HW_CAMERA_WRITE: GraphicBuffer can be used as a camera rendering target, the same way the camera writes GraphicBuffer
  • GRALLOC_USAGE_HW_CAMERA_READ: The camera can read GraphicBuffer

Graphicbuffer applicants can use different usage combinations depending on the scenario.

The above analyzed the scenario of allocating and releasing graphic buffers in the same process, thenGraphicBufferHow do you share that between processes? It can be summarized by a flow chart:

  1. First, the creation process passesGraphicBuffer::flattentheANativeWindowBufferKey attributes are stored in two arrays:bufferandfds.
  2. Second, cross-process transportbufferandfds.
  3. Then, use the process to pass throughGraphicBuffer::unflattenRebuild on your ownANativeWindowBufferThe key is rebuildingANativeWindowBuffer.handleStructure, equivalent to the creation processGraphicBufferMapped to the consuming process.
  4. Finally, follow registerBuffer-> Lock -> read and writeGraphicBuffer-> UNLOCK ->unregisterBuffer basic process operationGraphicBufferWill do.

Binder simply transmits the ANativeWindowBuffer properties, and the real underlying graphics memory (memory) is shared between processes. As you can see from the context, GraphicBufferAllocator is responsible for applying and releasing GraphicBuffer when creating the process, and GraphicBufferMapper is responsible for manipulating GraphicBuffer when using the process.

All operations performed by GraphicBufferMapper on GraphicBuffer are implemented by grallocHAL module, specifically gralloc_module_t. If you are interested, see GraphicBufferMapper.cpp. Here is just a look at the initialization logic of the Gralloc module:

GraphicBufferMapper::GraphicBufferMapper(): mAllocMod(0) {
    hw_module_t const* module;
    // As with HWC, open the gralloc module
    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    if (err == 0) {
        mAllocMod = reinterpret_cast<gralloc_module_t const* > (module); }}Copy the code

Finally, the key code for GraphicBuffer cross-process transmission is shown in the above flowchart:

/ / calculate transmission GraphicBuffer need Size size_t GraphicBuffer: : getFlattenedSize const () {return static_cast < size_t > (11 + (handle? handle->numInts : 0)) * sizeof(int); } / / retrieves the file descriptor number size_t GraphicBuffer: : getFdCount const () {return static_cast < size_t > (handle? handle->numFds : 0); } // Save GraphicBuffer key attributes in buffer and FDS for Binder transfer // size indicates the available length of buffer array, Status_t GraphicBuffer::flatten(void*& buffer, size_t& size, int*& FDS, Const size_t & count) {/ / whether length of buffer available enough size_t sizeNeeded = GraphicBuffer: : getFlattenedSize (); if (size < sizeNeeded) return NO_MEMORY; / / whether the FDS array length enough size_t fdCountNeeded = GraphicBuffer: : getFdCount (); if (count < fdCountNeeded) return NO_MEMORY; // Store the key attributes of the current GraphicBuffer in buffer int32_t* buf = static_cast<int32_t*>(buffer); // Store identifier buf[0] = 'GBFR'; buf[1] = width; buf[2] = height; buf[3] = stride; buf[4] = format; buf[5] = usage; buf[6] = static_cast<int32_t>(mId >> 32); buf[7] = static_cast<int32_t>(mId & 0xFFFFFFFFull); buf[8] = static_cast<int32_t>(mGenerationNumber); buf[9] = 0; buf[10] = 0; Buf [9] = handle->numFds; Buf [10] = handle->numInts; // copy an array of file descriptors to FDS memcpy(FDS, handle->data, static_cast<size_t>(handle->numFds) * sizeof(int)); // copy an int array to buffer memcpy(&buf[11], handle->data + handle->numFds, static_cast<size_t>(handle->numInts) * sizeof(int)); } // Modify buffer address and available length buffer = static_cast<void*>(static_cast<uint8_t*>(buffer) + sizenneeded); size -= sizeNeeded; If (handle) {FDS += handle->numFds; count -= static_cast<size_t>(handle->numFds); } return NO_ERROR; } // According to buffer and FDS transmitted by Binder, Status_t GraphicBuffer::unflatten(void const* &buffer, size_t& size, int const* &fds, Size_t & count) {if (size < 11 * sizeof(int)) return NO_MEMORY; int const* buf = static_cast<int const*>(buffer); if (buf[0] ! = 'GBFR') return BAD_TYPE; Const size_t numFds = static_cast<size_t>(buf[9]); const size_t numFds = static_cast<size_t>(buf[9]); const size_t numInts = static_cast<size_t>(buf[10]); Const size_t sizenneeded = (11 + numInts) * sizeof(int); if (size < sizeNeeded) return NO_MEMORY; Size_t fdCountNeeded = numFds; if (count < fdCountNeeded) return NO_MEMORY; If (handle) {/ / if there is, first release before ANativeWindowBuffer. Handle free_handle (); } if (numFds || numInts) { width = buf[1]; height = buf[2]; stride = buf[3]; format = buf[4]; usage = buf[5]; / / create ANativeWindowBuffer. Handle, Native_handle_create is defined in native_handle.c native_handle* h = native_handle_CREATE (static_cast<int>(numFds), static_cast<int>(numInts)); if (! h) { width = height = stride = format = usage = 0; handle = NULL; ALOGE("unflatten: native_handle_create failed"); return NO_MEMORY; } / / from the FDS and buffer copy file descriptors and int array to ANativeWindowBuffer. The handle structure memcpy (h - > data, FDS, numFds * sizeof (int)); memcpy(h->data + numFds, &buf[11], numInts * sizeof(int)); handle = h; } else { width = height = stride = format = usage = 0; handle = NULL; } mId = static_cast<uint64_t>(buf[6]) << 32; mId |= static_cast<uint32_t>(buf[7]); mGenerationNumber = static_cast<uint32_t>(buf[8]); MOwner = ownHandle; mOwner = ownHandle; if (handle ! = 0) {/ / register to the current thread status_t err. = mBufferMapper registerBuffer (handle); } // Adjust the address and available length of buffer and FDS arrays buffer = static_cast<void const*>(static_cast<uint8_t const*>(buffer) + sizenneeded); size -= sizeNeeded; fds += numFds; count -= numFds; return NO_ERROR; } / / ANativeWindowBuffer. Handle structure typedef struct native_handle {/ * sizeof (native_handle_t) * / int version; /* number of file-descriptors at &data[0] */ int numFds; /* number of ints at &data[numFds] */ int numInts; /* numFds + numInts ints */ int data[0]; } native_handle_t; typedef const native_handle_t* buffer_handle_tCopy the code

The above code is long, but it is the key code, you can refer to the comments to read. ANativeWindowBuffer. The handle is the core of GraphicBuffer fields, defined on the handle. H. Native_handle. c defines the create, close, and delete native_handle methods.

GraphicBuffer is applied and freed by a GraphicBufferAllocator. The GraphicBuffer was rebuilt using the unflatten process, so how does the process release the GraphicBuffer? After all, the actual graphic Buffer is not created in the current process. We can look directly at the GraphicBuffer release code:

// The GraphicBuffer destructor is called here
void GraphicBuffer::free_handle()
{
    if (mOwner == ownHandle) { // Indicates that the graphic Buffer is not created by itself, but is mapped from the creating process, i.e. in the using process
        mBufferMapper.unregisterBuffer(handle);
        / / close to delete ANativeWindowBuffer. Handle, specific method implementation can see native_handle. C
        native_handle_close(handle);
        native_handle_delete(const_cast<native_handle*>(handle));
    } else if (mOwner == ownData) { // Indicates that the graphic Buffer is created by itself and needs to be released by itself, i.e. in the creation process
        GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
        allocator.free(handle);
    }
    handle = NULL;
    mWrappedBuffer = 0;
}
Copy the code

Graphicbuffers pointing to the same GraphicBuffer can have multiple instances, but the underlying GraphicBuffer is the same.

In addition, GraphicBufferAllocator keeps track of all graphicBuffers allocated by adb shell Dumpsys SurfaceFlinger:

// Allocated buffers: // respectively represent buffer_HANDle_t address, size of graph Buffer, stride * height, pixel format, usage, etc. 0x76FD25A070: 459 (512) x 1546.00 KiB | 773 | | 1 | 0 x10000900 | PopupWindow: e2334a2 # 0 0 x76fd25a7e0: 1080 (1088) x 9945.00 KiB | 2340 | 1 | 1 | 0 x10000900 | StatusBar# 0 0 x76fd837220: 1080 (1088) x 9945.00 KiB | 2340 | | 1 | 0 x10001a00 | FramebufferSurface / / the graph of allocated Buffer the Total size of the Total allocated (estimate) : 112834.50 KBCopy the code

Here’s another question: In which process is the GraphicBuffer allocated? To explain this, you need to understand the BufferQueue logic, which I won’t go into here, but will examine in a separate article. Give a direct conclusion: The BufferQueueProducer creates the GraphicBuffer from the IGraphicBufferAlloc that the BufferQueueCore holds, GraphicBufferAlloc, the implementation of IGraphicBufferAlloc, runs in the SurfaceFlinger process. That is, the Surfaceflinger process is the only one that actually allocates graphicBuffers, and the other processes are just mapping operations.

conclusion

This paper analyzes grallocHAL module and main logic of LiBUI library. The next article will take a closer look at the libGUI library’s main logic.