引言
一直很好奇鼠标光标是如何实现的,它反映很快、延迟很小,没有受到 android 显示系统的影响。正好最近做相关的工作,跟着源码好好研究一下。
本文参考 android 9.0 源码。
从 input 说起
我们并不是要讲 input,只想看看鼠标光标的绘制过程。但是,android 将鼠标光标的实现放到了 input 中,这看起来也是合理的。在 input 中,光标由类sprite
实现。源码中对 sprite
的解释为:显示在其他图层之上的图形对象。看来 sprite
并非专为光标设计,但在源码中的位置表明,它在 android 中也只为鼠标或触摸之类的输入设备的光标服务。sprite
的定义中也只提供了简单的图形操作。
frameworks/base/libs/input/spritecontroller.h /* * a sprite is a simple graphical object that is displayed on-screen above other layers. * the basic sprite class is an interface. * the implementation is provided by the sprite controller. */ class sprite : public refbase { protected: sprite() { } virtual ~sprite() { } public: enum { // the base layer for pointer sprites. base_layer_pointer = 0, // reserve space for 1 pointer // the base layer for spot sprites. base_layer_spot = 1, // reserve space for max_pointer_id spots }; /* sets the bitmap that is drawn by the sprite. * the sprite retains a copy of the bitmap for subsequent rendering. */ virtual void seticon(const spriteicon& icon) = 0; inline void clearicon() { seticon(spriteicon()); } /* sets whether the sprite is visible. */ virtual void setvisible(bool visible) = 0; /* sets the sprite position on screen, relative to the sprite's hot spot. */ virtual void setposition(float x, float y) = 0; /* sets the layer of the sprite, relative to the system sprite overlay layer. * layer 0 is the overlay layer, > 0 appear above this layer. */ virtual void setlayer(int32_t layer) = 0; /* sets the sprite alpha blend ratio between 0.0 and 1.0. */ virtual void setalpha(float alpha) = 0; /* sets the sprite transformation matrix. */ virtual void settransformationmatrix(const spritetransformationmatrix& matrix) = 0; };
控制光标的类叫做 spritecontroller
,pointercontroller 会使用这个类来显示光标。这里我们只关心光标图形的合成,真正显示和更新光标的方法是 spritecontroller::doupdatesprites()
。
frameworks/base/libs/input/spritecontroller.cpp void spritecontroller::doupdatesprites() { // 从invalidatedsprites 中收集需要更新的 sprite vectorupdates; size_t numsprites; { // acquire lock automutex _l(mlock); numsprites = mlocked.invalidatedsprites.size(); for (size_t i = 0; i < numsprites; i ) { const sp & sprite = mlocked.invalidatedsprites.itemat(i); updates.push(spriteupdate(sprite, sprite->getstatelocked())); sprite->resetdirtylocked(); } mlocked.invalidatedsprites.clear(); } // release lock // surfaces 未创建或丢失时,重新创建 surface bool surfacechanged = false; for (size_t i = 0; i < numsprites; i ) { spriteupdate& update = updates.edititemat(i); if (update.state.surfacecontrol == null && update.state.wantsurfacevisible()) { update.state.surfacewidth = update.state.icon.bitmap.width(); update.state.surfaceheight = update.state.icon.bitmap.height(); update.state.surfacedrawn = false; update.state.surfacevisible = false; // 创建 surface,我们这次的关注点 update.state.surfacecontrol = obtainsurface( update.state.surfacewidth, update.state.surfaceheight); if (update.state.surfacecontrol != null) { update.surfacechanged = surfacechanged = true; } } } // 如果需要,重新调整 sprites 大小 surfacecomposerclient::transaction t; bool needapplytransaction = false; for (size_t i = 0; i < numsprites; i ) { ...... if (update.state.surfacewidth < desiredwidth || update.state.surfaceheight < desiredheight) { needapplytransaction = true; t.setsize(update.state.surfacecontrol, desiredwidth, desiredheight); ...... } } } if (needapplytransaction) { t.apply(); } // 如果需要,重画 sprites for (size_t i = 0; i < numsprites; i ) { spriteupdate& update = updates.edititemat(i); if ((update.state.dirty & dirty_bitmap) && update.state.surfacedrawn) { update.state.surfacedrawn = false; update.surfacechanged = surfacechanged = true; } if (update.state.surfacecontrol != null && !update.state.surfacedrawn && update.state.wantsurfacevisible()) { sp surface = update.state.surfacecontrol->getsurface(); anativewindow_buffer outbuffer; ...... // 使用 skia 画图 skbitmap surfacebitmap; ssize_t bpr = outbuffer.stride * bytesperpixel(outbuffer.format); surfacebitmap.installpixels(skimageinfo::maken32premul(outbuffer.width, outbuffer.height), outbuffer.bits, bpr); skcanvas surfacecanvas(surfacebitmap); skpaint paint; paint.setblendmode(skblendmode::ksrc); surfacecanvas.drawbitmap(update.state.icon.bitmap, 0, 0, &paint); if (outbuffer.width > update.state.icon.bitmap.width()) { paint.setcolor(0); // transparent fill color surfacecanvas.drawrect(skrect::makeltrb(update.state.icon.bitmap.width(), 0, outbuffer.width, update.state.icon.bitmap.height()), paint); } if (outbuffer.height > update.state.icon.bitmap.height()) { paint.setcolor(0); // transparent fill color surfacecanvas.drawrect(skrect::makeltrb(0, update.state.icon.bitmap.height(), outbuffer.width, outbuffer.height), paint); } ...... } // 根据 dirty 值来设置 surface needapplytransaction = false; for (size_t i = 0; i < numsprites; i ) { spriteupdate& update = updates.edititemat(i); bool wantsurfacevisibleanddrawn = update.state.wantsurfacevisible() && update.state.surfacedrawn; bool becomingvisible = wantsurfacevisibleanddrawn && !update.state.surfacevisible; bool becominghidden = !wantsurfacevisibleanddrawn && update.state.surfacevisible; if (update.state.surfacecontrol != null && (becomingvisible || becominghidden || (wantsurfacevisibleanddrawn && (update.state.dirty & (dirty_alpha | dirty_position | dirty_transformation_matrix | dirty_layer | dirty_visibility | dirty_hotspot))))) { ...... } if (needapplytransaction) { status_t status = t.apply(); if (status) { aloge("error applying surface transaction"); } } ...... }
一次的光标的更新就会涉及到如此多的代码逻辑,可见ui真是不容易。其他的逻辑线不管,这次我们只关心光标的图层。上述代码通过 obtainsurface()
来创建 surface。
frameworks/base/libs/input/spritecontroller.cpp spspritecontroller::obtainsurface(int32_t width, int32_t height) { ensuresurfacecomposerclient(); sp surfacecontrol = msurfacecomposerclient->createsurface( string8("sprite"), width, height, pixel_format_rgba_8888, isurfacecomposerclient::ehidden | isurfacecomposerclient::ecursorwindow); if (surfacecontrol == null || !surfacecontrol->isvalid()) { aloge("error creating sprite surface."); return null; } return surfacecontrol; }
这里我们需要重点关注的是 createsurface()
方法中的参数 flags
。sprite 中这个 flags
设置了ehidden
和ecursorwindow
,它们表明创建的 surface 是隐藏的,并标识为 cursor 使用。
来到 surface
input 中为光标创建了一个 surface,并且标识这是一个 cursor 使用的 surface。之后,surface 中会根据情形对光标图层做特殊处理,这里的关键字就是 cursor
。
我们还是以光标图层为主线进行跟踪,先继续看下createsurface()
。经过一系列的 binder 调用和 message传递,最终通过 surfaceflinger 的createlayer()
完成图层创建。
frameworks/native/services/surfaceflinger/surfaceflinger.cpp status_t surfaceflinger::createlayer(const string8& name, const sp& client, uint32_t w, uint32_t h, pixelformat format, uint32_t flags, int32_t windowtype, int32_t owneruid, sp * handle, sp * gbp, const sp & parenthandle, const sp & parentlayer) { ...... switch (flags & isurfacecomposerclient::efxsurfacemask) { // 普通图层 case isurfacecomposerclient::efxsurfacenormal: result = createbufferlayer(client, uniquename, w, h, flags, format, handle, gbp, &layer); break; // 纯色图层 case isurfacecomposerclient::efxsurfacecolor: result = createcolorlayer(client, uniquename, w, h, flags, handle, &layer); break; default: result = bad_value; break; } ...... // client中通过layer管理surface,将创建的layer加入到layerstack中 result = addclientlayer(client, *handle, *gbp, layer, parenthandle, parentlayer); if (result != no_error) { return result; } minterceptor->savesurfacecreation(layer); settransactionflags(etransactionneeded); return result; }
createlayer()
中,光标算是普通图层,所以仅需调用createbufferlayer()
来创建。
frameworks/native/services/surfaceflinger/surfaceflinger.cpp status_t surfaceflinger::createbufferlayer(const sp& client, const string8& name, uint32_t w, uint32_t h, uint32_t flags, pixelformat& format, sp * handle, sp * gbp, sp * outlayer) { ...... // 创建一个bufferlayer sp layer = new bufferlayer(this, client, name, w, h, flags); // 设置buffer属性 status_t err = layer->setbuffers(w, h, format, flags); if (err == no_error) { *handle = layer->gethandle(); // 获取layer的句柄 *gbp = layer->getproducer(); // 获取graphicbufferproducer对象 *outlayer = layer; } aloge_if(err, "createbufferlayer() failed (%s)", strerror(-err)); return err; }
其中layer->setbuffers()
设置了该bufferlayer的属性。可以看到,当申请的是一个 cursor 图层时,mpotentialcursor
被设置为true
,表明该 bufferlayer 作为 cursor 使用。
frameworks/native/services/surfaceflinger/bufferlayer.cpp status_t bufferlayer::setbuffers(uint32_t w, uint32_t h, pixelformat format, uint32_t flags) { ...... mformat = format; mpotentialcursor = (flags & isurfacecomposerclient::ecursorwindow) ? true : false; mprotectedbyapp = (flags & isurfacecomposerclient::eprotectedbyapp) ? true : false; mcurrentopacity = getopacityforformat(format); mconsumer->setdefaultbuffersize(w, h); mconsumer->setdefaultbufferformat(format); mconsumer->setconsumerusagebits(geteffectiveusage(0)); return no_error; }
surfaceflinger 中的 cursor 操作
上面讲到 cursor layer 最核心的属性mpotentialcursor
,createsurface()
只是设置了这个属性,真正的使用在 surfaceflinger 渲染过程中。接着我发现,想把这个东西看明白,先需要把 android 图形合成弄清楚,这可是的庞大的工程。借张图,有兴趣的自己研究。
但是,时间有限,怎么办?我的解决办法就是搜索关键字。搜索关键字cursor
后,可以得到一些相关的操作。surfaceflinger 接收到 vsync 信号后,会调用handlemessagerefresh()
来刷新显示。
frameworks/native/services/surfaceflinger/surfaceflinger.cpp void surfaceflinger::handlemessagerefresh() { ...... precomposition(refreshstarttime); //合成预处理 rebuildlayerstacks(); //重新构建layerstacks setuphwcomposer(); //更新hwcomposer的图层和属性 dodebugflashregions(); //图形绘制的debug模式 dotracing("handlerefresh"); loglayerstats(); docomposition(); //合成所有图层 postcomposition(refreshstarttime); //合成后处理 ...... }
我们还是只关心 cursor 的操作,它位于 hwcomposer 控制的图层中。
frameworks/native/services/surfaceflinger/surfaceflinger.cpp void surfaceflinger::setuphwcomposer() { ...... // 遍历所有的displaydevice,为绘制做准备 for (size_t dpy=0 ; dpybeginframe(mustrecompose); if (mustrecompose) { mdisplays[dpy]->lastcompositionhadvisiblelayers = !empty; } } // 设置hwc layer if (cc_unlikely(mgeometryinvalid)) { mgeometryinvalid = false; for (size_t dpy=0 ; dpy hashwclayer(hwcid)) { if (!layer->createhwclayer(getbe().mhwc.get(), hwcid)) { layer->forceclientcomposition(hwcid); continue; } } // 设置hwc layer的显示区域、合成模式、alpha、order等 layer->setgeometry(displaydevice, i); // hwc被禁止或绘制debug模式时,强制opengl渲染 if (mdebugdisablehwc || mdebugregion) { layer->forceclientcomposition(hwcid); } ...... } // 准备hwc需要渲染的数据 for (size_t displayid = 0; displayid < mdisplays.size(); displayid) { auto& displaydevice = mdisplays[displayid]; const auto hwcid = displaydevice->gethwcdisplayid(); ...... //调用 setperframedata方法 layer->setperframedata(displaydevice); ...... } ...... for (size_t displayid = 0; displayid < mdisplays.size(); displayid) { ...... // 尝试进行显示 status_t result = displaydevice->prepareframe(*getbe().mhwc); ...... } }
其中setperframedata()
完成 hwcomposer 的相关设置,为显示做准备。
frameworks/native/services/surfaceflinger/bufferlayer.cpp void bufferlayer::setperframedata(const sp& displaydevice) { ...... // 设置可见区域 auto error = hwclayer->setvisibleregion(visible); ...... // 设置刷新区域 error = hwclayer->setsurfacedamage(surfacedamageregion); ...... // sideband layers设置 if (getbe().compositioninfo.hwc.sidebandstream.get()) { setcompositiontype(hwcid, hwc2::composition::sideband); error = hwclayer->setsidebandstream(getbe().compositioninfo.hwc.sidebandstream->handle()); ...... return; } if (mpotentialcursor) { // cursor layers设置 setcompositiontype(hwcid, hwc2::composition::cursor); } else { // device layers设置 setcompositiontype(hwcid, hwc2::composition::device); } // 设置色彩空间 error = hwclayer->setdataspace(mcurrentdataspace); if (error != hwc2::error::none) { aloge("[%s] failed to set dataspace %d: %s (%d)", mname.string(), mcurrentdataspace, to_string(error).c_str(), static_cast (error)); } // 获取hdr数据并设置到hwc中 const hdrmetadata& metadata = mconsumer->getcurrenthdrmetadata(); error = hwclayer->setperframemetadata(displaydevice->getsupportedperframemetadata(), metadata); ...... // 获取渲染的数据buffer和fence,设置到hwc中 sp hwcbuffer; hwcinfo.buffercache.gethwcbuffer(getbe().compositioninfo.mbufferslot, getbe().compositioninfo.mbuffer, &hwcslot, &hwcbuffer); auto acquirefence = mconsumer->getcurrentfence(); error = hwclayer->setbuffer(hwcslot, hwcbuffer, acquirefence); ...... }
我们终于找到了希望看到的mpotentialcursor
,通过这个标识告诉 hwc2 这是一个 cursorlayer。除此之外,对于 cursorlayer 的操作与 devicelayer 并没有区别。所以,surfaceflinger 更多的是希望 hwcomposer 根据 layer 的类型进行不同处理。目前 hwc2 支持的 layer 类型有,
- hwc2_composition_client:不通过 hwc 硬件来合成图层。gpu 将这类图层合成到一个图像 buffer 中,然后传递给 hwc。
- hwc2_composition_device:使用 hwc 硬件来合成图层。
- hwc2_composition_solid_color:用来处理 colorlayer 数据,如果 hwc 不支持,则改为使用 client 方式合成。
- hwc2_composition_cursor:用来处理 cursorlayer 数据,位置通过
setcursorposition
异步设置。如果 hwc 不支持,则改为使用 client 或 device 方式合成。 - hwc2_composition_sideband:对于这种 layer,需要由外部机制提供内容更新,例如电视信号的视频数据。如果 hwc 不支持,则改为使用 client 或 device 方式合成,但可能无法正确显示。
cursor layer还有一个重要的操作,setcursorposition()
,这个方法用来设置 cursor 的位置,具体的实现依然在 hwcomposer 中。当用户进程更新 surface 图形时,surfaceflinger 会发送invalidate
消息给相应的 layer。消息处理函数调用handletransaction()
和handlepageflip()
来更新layer对象。handletransaction()
用来处理 layer 和显示设备的变化,它继续调用handletransactionlocked()
。
frameworks/native/services/surfaceflinger/surfaceflinger.cpp void surfaceflinger::handletransactionlocked(uint32_t transactionflags) { ...... // 处理layer的变化 if (transactionflags & etraversalneeded) { ...... } // 处理显示设备的变化 if (transactionflags & edisplaytransactionneeded) { processdisplaychangeslocked(); processdisplayhotplugeventslocked(); } // 设置transform hint if (transactionflags & (edisplaylayerstackchanged|edisplaytransactionneeded)) { ...... } //处理layer的增减 if (mlayersadded) { ...... } if (mlayersremoved) { ...... } committransaction(); // 更新光标位置 updatecursorasync(); }
我们找到了 cursor 更新的地方,surfaceflinger 更新图形时会同步更新光标位置。之后,在 vsync 到来时,完成图像的更新显示。
frameworks/native/services/surfaceflinger/surfaceflinger.cpp void surfaceflinger::updatecursorasync() { for (size_t displayid = 0; displayid < mdisplays.size(); displayid) { ...... // 调用layer的对应方法 for (auto& layer : displaydevice->getvisiblelayerssortedbyz()) { layer->updatecursorposition(displaydevice); } } }
frameworks/native/services/surfaceflinger/layer.cpp void layer::updatecursorposition(const sp& displaydevice) { // hwc layer不存在或者不是cursor layer,不做处理 auto hwcid = displaydevice->gethwcdisplayid(); if (getbe().mhwclayers.count(hwcid) == 0 || getcompositiontype(hwcid) != hwc2::composition::cursor) { return; } ...... // 获取图层的位置 rect bounds = reduce(win, s.activetransparentregion); rect frame(gettransform().transform(bounds)); frame.intersect(displaydevice->getviewport(), &frame); if (!s.finalcrop.isempty()) { frame.intersect(s.finalcrop, &frame); } auto& displaytransform(displaydevice->gettransform()); auto position = displaytransform.transform(frame); // 调用hwc的方法来设置图层位置 auto error = getbe().mhwclayers[hwcid].layer->setcursorposition(position.left, position.top); }
到达 hwcomposer
上面分析了许多代码,但真正与 cursor 相关的并不多。cursorlayer 的真正实现还是在 hwcomposer 中。但是 hwcomposer 的实现是与平台相关的,不同的平台对 cursorlayer 的实现可能不同。效率的方式是使用一个独立的硬件 osd 来显示 cursorlayer,然后通过硬件合成的方式将 cursorlayer 叠加到 ui 显示层。使用这种方式,光标的移动效率也很高,只要改变硬件 osd 显示的位置即可。如果没有独立的硬件 osd 来使用,就只能在标准显示层上进行软件叠加,或者使用 gpu 来叠加。
参考:
由于跟平台相关的实现具有私密性,这里不再继续分析,更多关于android鼠标光标图形合成的资料请关注其它相关文章!