Android6.0 影象合成過程詳解(二) doComposition函式
上篇部落格分析到setUpHWComposer函式,這裡我們繼續分析影象合成的過程從doComposition函式開始,以及在這過程中解答一些上篇部落格提出的疑問。
一、doComposition合成圖層
doComposition這個函式就是合成所有層的影象
void SurfaceFlinger::doComposition() { ATRACE_CALL(); const bool repaintEverything = android_atomic_and(0, &mRepaintEverything); for (size_t dpy=0 ; dpy<mDisplays.size() ; dpy++) { const sp<DisplayDevice>& hw(mDisplays[dpy]); if (hw->isDisplayOn()) { // transform the dirty region into this screen's coordinate space const Region dirtyRegion(hw->getDirtyRegion(repaintEverything)); // repaint the framebuffer (if needed) doDisplayComposition(hw, dirtyRegion); hw->dirtyRegion.clear(); hw->flip(hw->swapRegion); hw->swapRegion.clear(); } // inform the h/w that we're done compositing hw->compositionComplete(); } postFramebuffer(); }
上面函式遍歷所有的DisplayDevice然後呼叫doDisplayComposition函式。然後我們再看看doDisplayComposition函式
void SurfaceFlinger::doDisplayComposition(const sp<const DisplayDevice>& hw, const Region& inDirtyRegion) { bool isHwcDisplay = hw->getHwcDisplayId() >= 0; if (!isHwcDisplay && inDirtyRegion.isEmpty()) { return; } Region dirtyRegion(inDirtyRegion); //swapRegion設定為需要更新的區域 hw->swapRegion.orSelf(dirtyRegion); uint32_t flags = hw->getFlags();//獲得顯示裝置支援的更新方式標誌 if (flags & DisplayDevice::SWAP_RECTANGLE) {//支援矩陣更新 dirtyRegion.set(hw->swapRegion.bounds()); } else { if (flags & DisplayDevice::PARTIAL_UPDATES) {//支援部分更新 dirtyRegion.set(hw->swapRegion.bounds()); } else { //將更新區域調整為整個視窗大小 dirtyRegion.set(hw->bounds()); hw->swapRegion = dirtyRegion; } } if (CC_LIKELY(!mDaltonize && !mHasColorMatrix)) { if (!doComposeSurfaces(hw, dirtyRegion)) return;//合成 } else { RenderEngine& engine(getRenderEngine()); mat4 colorMatrix = mColorMatrix; if (mDaltonize) { colorMatrix = colorMatrix * mDaltonizer(); } mat4 oldMatrix = engine.setupColorTransform(colorMatrix); doComposeSurfaces(hw, dirtyRegion);//合成 engine.setupColorTransform(oldMatrix); } // update the swap region and clear the dirty region hw->swapRegion.orSelf(dirtyRegion); // swap buffers (presentation) hw->swapBuffers(getHwComposer());//使用egl將egl中的合成好的影象,輸出到DisplayDevice的mSurface中 }
這個函式設定下需要更新的區域,後面呼叫doComposeSurfaces函式來合成圖層,呼叫完doComposeSurfaces函式後,如果需要egl合成影象話,在這個函式中合成好。而最後呼叫swapBuffers只是將egl合成好的影象輸出到DisplayDevice的mSurface中。
我們再來看看doComposeSurfaces函式,我們先來看一開始的程式碼,先判斷是否有egl合成,然後再看是否有hwc合成(硬體合成)
bool SurfaceFlinger::doComposeSurfaces(const sp<const DisplayDevice>& hw, const Region& dirty) { RenderEngine& engine(getRenderEngine()); const int32_t id = hw->getHwcDisplayId(); HWComposer& hwc(getHwComposer()); HWComposer::LayerListIterator cur = hwc.begin(id); const HWComposer::LayerListIterator end = hwc.end(id); bool hasGlesComposition = hwc.hasGlesComposition(id); if (hasGlesComposition) {//是否有egl合成 if (!hw->makeCurrent(mEGLDisplay, mEGLContext)) { ALOGW("DisplayDevice::makeCurrent failed. Aborting surface composition for display %s", hw->getDisplayName().string()); eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if(!getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext)) { ALOGE("DisplayDevice::makeCurrent on default display failed. Aborting."); } return false; } // Never touch the framebuffer if we don't have any framebuffer layers const bool hasHwcComposition = hwc.hasHwcComposition(id); if (hasHwcComposition) {//是否有hwc合成 // when using overlays, we assume a fully transparent framebuffer // NOTE: we could reduce how much we need to clear, for instance // remove where there are opaque FB layers. however, on some // GPUs doing a "clean slate" clear might be more efficient. // We'll revisit later if needed. engine.clearWithColor(0, 0, 0, 0); } else { // we start with the whole screen area const Region bounds(hw->getBounds()); // we remove the scissor part // we're left with the letterbox region // (common case is that letterbox ends-up being empty) const Region letterbox(bounds.subtract(hw->getScissor())); // compute the area to clear Region region(hw->undefinedRegion.merge(letterbox)); // but limit it to the dirty region region.andSelf(dirty); // screen is already cleared here if (!region.isEmpty()) { // can happen with SurfaceView drawWormhole(hw, region); } } if (hw->getDisplayType() != DisplayDevice::DISPLAY_PRIMARY) { // just to be on the safe side, we don't set the // scissor on the main display. It should never be needed // anyways (though in theory it could since the API allows it). const Rect& bounds(hw->getBounds()); const Rect& scissor(hw->getScissor()); if (scissor != bounds) { // scissor doesn't match the screen's dimensions, so we // need to clear everything outside of it and enable // the GL scissor so we don't draw anything where we shouldn't // enable scissor for this frame const uint32_t height = hw->getHeight(); engine.setScissor(scissor.left, height - scissor.bottom, scissor.getWidth(), scissor.getHeight()); } } } ......
我們來看hasGlesComposition函式和hasHwcComposition函式,就是看其對應的DisplayData中是否有hasFbComp和hasOvComp。
bool HWComposer::hasGlesComposition(int32_t id) const {
if (!mHwc || uint32_t(id)>31 || !mAllocatedDisplayIDs.hasBit(id))
return true;
return mDisplayData[id].hasFbComp;
}
bool HWComposer::hasHwcComposition(int32_t id) const {
if (!mHwc || uint32_t(id)>31 || !mAllocatedDisplayIDs.hasBit(id))
return false;
return mDisplayData[id].hasOvComp;
}
而這兩個值是在prepare中呼叫Hwc的prepare函式之後賦值的status_t HWComposer::prepare() {
......
int err = mHwc->prepare(mHwc, mNumDisplays, mLists);
ALOGE_IF(err, "HWComposer: prepare failed (%s)", strerror(-err));
if (err == NO_ERROR) {
// here we're just making sure that "skip" layers are set
// to HWC_FRAMEBUFFER and we're also counting how many layers
// we have of each type.
//
// If there are no window layers, we treat the display has having FB
// composition, because SurfaceFlinger will use GLES to draw the
// wormhole region.
for (size_t i=0 ; i<mNumDisplays ; i++) {
DisplayData& disp(mDisplayData[i]);
disp.hasFbComp = false;
disp.hasOvComp = false;
if (disp.list) {
for (size_t i=0 ; i<disp.list->numHwLayers ; i++) {
hwc_layer_1_t& l = disp.list->hwLayers[i];
//ALOGD("prepare: %d, type=%d, handle=%p",
// i, l.compositionType, l.handle);
if (l.flags & HWC_SKIP_LAYER) {
l.compositionType = HWC_FRAMEBUFFER;
}
if (l.compositionType == HWC_FRAMEBUFFER) {
disp.hasFbComp = true;//只要有一個layer是HWC_FRAMEBUFFER
}
if (l.compositionType == HWC_OVERLAY) {
disp.hasOvComp = true;//有一個layer是HWC_OVERLAY
}
if (l.compositionType == HWC_CURSOR_OVERLAY) {
disp.hasOvComp = true;//有一個layer是HWC_CURSOR_OVERLAY
}
}
if (disp.list->numHwLayers == (disp.framebufferTarget ? 1 : 0)) {//layer的數量 有framebufferTarget為1 沒有為0
disp.hasFbComp = true;
}
} else {
disp.hasFbComp = true;//沒有list
}
}
}
return (status_t)err;
}
我們繼續看doComposeSurfaces函式,下面這個函式當cur!=end代表起碼有兩個以上圖層,然後遍歷圖層,當layer是HWC_FRAMEBUFFER代表是需要egl合成的,而HWC_FRAMEBUFFER_TARGET是egl合成後使用的直接就跳了,HWC_CURSOR_OVERLAY和HWC_OVERLAY是用HWC模組(硬體合成)的,也就不用呼叫Layer的draw方法。而如果圖層只要1個或者沒有,那麼直接使用egl合成。
HWComposer::LayerListIterator cur = hwc.begin(id);
const HWComposer::LayerListIterator end = hwc.end(id);
......
const Vector< sp<Layer> >& layers(hw->getVisibleLayersSortedByZ());
const size_t count = layers.size();
const Transform& tr = hw->getTransform();
if (cur != end) { //代表起碼有兩個以上圖層
// we're using h/w composer
for (size_t i=0 ; i<count && cur!=end ; ++i, ++cur) {//遍歷圖層
const sp<Layer>& layer(layers[i]);
const Region clip(dirty.intersect(tr.transform(layer->visibleRegion)));
if (!clip.isEmpty()) {
switch (cur->getCompositionType()) {
case HWC_CURSOR_OVERLAY:
case HWC_OVERLAY: {
const Layer::State& state(layer->getDrawingState());
if ((cur->getHints() & HWC_HINT_CLEAR_FB)
&& i
&& layer->isOpaque(state) && (state.alpha == 0xFF)
&& hasGlesComposition) {
// never clear the very first layer since we're
// guaranteed the FB is already cleared
layer->clearWithOpenGL(hw, clip);
}
break;
}
case HWC_FRAMEBUFFER: {
layer->draw(hw, clip);//只有是HWC_FRAMEBUFFER才會呼叫Layer的draw合成
break;
}
case HWC_FRAMEBUFFER_TARGET: {
// this should not happen as the iterator shouldn't
// let us get there.
ALOGW("HWC_FRAMEBUFFER_TARGET found in hwc list (index=%zu)", i);
break;
}
}
}
layer->setAcquireFence(hw, *cur);
}
} else {
// we're not using h/w composer
for (size_t i=0 ; i<count ; ++i) {//只有一個或者沒有圖層 就直接使用Layer的draw合成
const sp<Layer>& layer(layers[i]);
const Region clip(dirty.intersect(
tr.transform(layer->visibleRegion)));
if (!clip.isEmpty()) {
layer->draw(hw, clip);
}
}
}
// disable scissor at the end of the frame
engine.disableScissor();
return true;
}
Layer的draw我們就不看了主要是使用egl合成紋理,但是有一點疑問,我們從來沒有把layer中的mActiveBuffer放到egl中去,那麼egl又是怎麼合成各個layer的呢,我想肯定客戶程序在繪製各個layer的時候,也是用egl繪製的,所有後面合成的時候egl有各個layer的buffer。
後面我們再來看下DisplayDevice::swapBuffers函式,是使用eglSwapBuffers來把egl合成的資料放到mSurface中去。
void DisplayDevice::swapBuffers(HWComposer& hwc) const {
// We need to call eglSwapBuffers() if:
// (1) we don't have a hardware composer, or
// (2) we did GLES composition this frame, and either
// (a) we have framebuffer target support (not present on legacy
// devices, where HWComposer::commit() handles things); or
// (b) this is a virtual display
if (hwc.initCheck() != NO_ERROR ||
(hwc.hasGlesComposition(mHwcDisplayId) &&
(hwc.supportsFramebufferTarget() || mType >= DISPLAY_VIRTUAL))) {
EGLBoolean success = eglSwapBuffers(mDisplay, mSurface);
if (!success) {
EGLint error = eglGetError();
if (error == EGL_CONTEXT_LOST ||
mType == DisplayDevice::DISPLAY_PRIMARY) {
LOG_ALWAYS_FATAL("eglSwapBuffers(%p, %p) failed with 0x%08x",
mDisplay, mSurface, error);
} else {
ALOGE("eglSwapBuffers(%p, %p) failed with 0x%08x",
mDisplay, mSurface, error);
}
}
}
else if(hwc.supportsFramebufferTarget() || mType >= DISPLAY_VIRTUAL)
{
EGLBoolean success = eglSwapBuffersVIV(mDisplay, mSurface);
if (!success) {
EGLint error = eglGetError();
ALOGE("eglSwapBuffersVIV(%p, %p) failed with 0x%08x",
mDisplay, mSurface, error);
}
}
status_t result = mDisplaySurface->advanceFrame();
if (result != NO_ERROR) {
ALOGE("[%s] failed pushing new frame to HWC: %d",
mDisplayName.string(), result);
}
}
二、FramebufferSurface收到egl合成數據
之前分析DisplayDevice時候,還分析了FramebufferSurface,我們這裡再來看下。
在SurfaceFlinger.cpp中的init函式,在建立DisplayDevice之前,我們先呼叫createBufferQueue來建立了一個buffer的生產者和消費者,然後把消費者放入了FramebufferSurface,生產者放入了DisplayDevice中。
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer,
new GraphicBufferAlloc());
sp<FramebufferSurface> fbs = new FramebufferSurface(*mHwc, i,
consumer);
int32_t hwcId = allocateHwcDisplayId(type);
sp<DisplayDevice> hw = new DisplayDevice(this,
type, hwcId, mHwc->getFormat(hwcId), isSecure, token,
fbs, producer,
mRenderEngine->getEGLConfig());
我們先來看生產者,下面是DisplayDevice的建構函式,生產者作為引數直接新建了一個Surface,然後把這個Surface作為引數呼叫eglCreateWindowSurface返回的就是mSurface,之前我們分析最後egl合成的資料時呼叫eglSwapBuffers並且把資料放到mSurface,這樣最後肯定就到消費者(FramebufferSurface)去了。
mNativeWindow = new Surface(producer, false);
ANativeWindow* const window = mNativeWindow.get();
/*
* Create our display's surface
*/
EGLSurface surface;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (config == EGL_NO_CONFIG) {
config = RenderEngine::chooseEglConfig(display, format);
}
surface = eglCreateWindowSurface(display, config, window, NULL);
最後到消費者那端的onFrameAvailable,也就是FramebufferSurface的onFrameAvailable中,我們現在來分析下這個過程,也就解答了一個onFrameAvailable的疑惑。
FramebufferSurface的父類是ConsumerBase類,我們來看其建構函式。先是構造了mConsumer,這裡其實就是BufferQueueConsumer類,後面呼叫了其consumerConnect方法。
ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
mAbandoned(false),
mConsumer(bufferQueue) {
mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
sp<IConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener);
status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
if (err != NO_ERROR) {
CB_LOGE("ConsumerBase: error connecting to BufferQueue: %s (%d)",
strerror(-err), err);
} else {
mConsumer->setConsumerName(mName);
}
}
我們來看下BufferQueueConsumer類的consumerConnect方法,就是呼叫了connect方法。
virtual status_t consumerConnect(const sp<IConsumerListener>& consumer,
bool controlledByApp) {
return connect(consumer, controlledByApp);
}
這個方法中將mCore->mConsumerListener = consumerListener,這個mCore就是BufferQueueCore類。我們再從ConsumerBase的建構函式看這個consumerListener引數其實就是FrameBufferSurface物件本身。
status_t BufferQueueConsumer::connect(
const sp<IConsumerListener>& consumerListener, bool controlledByApp) {
ATRACE_CALL();
if (consumerListener == NULL) {
BQ_LOGE("connect(C): consumerListener may not be NULL");
return BAD_VALUE;
}
BQ_LOGV("connect(C): controlledByApp=%s",
controlledByApp ? "true" : "false");
Mutex::Autolock lock(mCore->mMutex);
if (mCore->mIsAbandoned) {
BQ_LOGE("connect(C): BufferQueue has been abandoned");
return NO_INIT;
}
mCore->mConsumerListener = consumerListener;//設定回撥
mCore->mConsumerControlledByApp = controlledByApp;
return NO_ERROR;
}
我們再看BufferQueueProducer::queueBuffer函式,這個函式應該是生產者已經使用好buffer了,這個使用會呼叫如下程式碼這個listener就是BufferQueueCore的mConsumerListener,傳輸的資料時BufferItem。再傳之前把BufferItem的mGraphicBuffer清了,因為消費者可以自己獲取buffer,不用通過BufferItem傳。
item.mGraphicBuffer.clear();
item.mSlot = BufferItem::INVALID_BUFFER_SLOT;
// Call back without the main BufferQueue lock held, but with the callback
// lock held so we can ensure that callbacks occur in order
{
Mutex::Autolock lock(mCallbackMutex);
while (callbackTicket != mCurrentCallbackTicket) {
mCallbackCondition.wait(mCallbackMutex);
}
if (frameAvailableListener != NULL) {
frameAvailableListener->onFrameAvailable(item);
} else if (frameReplacedListener != NULL) {
frameReplacedListener->onFrameReplaced(item);
}
++mCurrentCallbackTicket;
mCallbackCondition.broadcast();
}
這樣就要FramebufferSurface的onFrameAvailable函式中去了,我們來看下這個函式。
void FramebufferSurface::onFrameAvailable(const BufferItem& /* item */) {
sp<GraphicBuffer> buf;
sp<Fence> acquireFence;
status_t err = nextBuffer(buf, acquireFence);
if (err != NO_ERROR) {
ALOGE("error latching nnext FramebufferSurface buffer: %s (%d)",
strerror(-err), err);
return;
}
err = mHwc.fbPost(mDisplayType, acquireFence, buf);
if (err != NO_ERROR) {
ALOGE("error posting framebuffer: %d", err);
}
}
這個函式先用nextBuffer獲取資料,然後呼叫了HWComposer的fbPost函式。我們先來看下nextBuffer函式,這個函式主要通過acquireBufferLocked獲取BufferItem,其中的mBuf就是buffer了。status_t FramebufferSurface::nextBuffer(sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence) {
Mutex::Autolock lock(mMutex);
BufferItem item;
status_t err = acquireBufferLocked(&item, 0);
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
outBuffer = mCurrentBuffer;
return NO_ERROR;
} else if (err != NO_ERROR) {
ALOGE("error acquiring buffer: %s (%d)", strerror(-err), err);
return err;
}
if (mCurrentBufferSlot != BufferQueue::INVALID_BUFFER_SLOT &&
item.mBuf != mCurrentBufferSlot) {
// Release the previous buffer.
err = releaseBufferLocked(mCurrentBufferSlot, mCurrentBuffer,
EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
if (err < NO_ERROR) {
ALOGE("error releasing buffer: %s (%d)", strerror(-err), err);
return err;
}
}
mCurrentBufferSlot = item.mBuf;
mCurrentBuffer = mSlots[mCurrentBufferSlot].mGraphicBuffer;
outFence = item.mFence;
outBuffer = mCurrentBuffer;
return NO_ERROR;
}
而這個acquireBufferLocked還是用mConsumer的acquireBuffer來獲取BufferItem。mConsumer就是BufferQueueConsumer類。status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
nsecs_t presentWhen, uint64_t maxFrameNumber) {
status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber);
if (err != NO_ERROR) {
return err;
}
if (item->mGraphicBuffer != NULL) {
mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
}
mSlots[item->mBuf].mFrameNumber = item->mFrameNumber;
mSlots[item->mBuf].mFence = item->mFence;
CB_LOGV("acquireBufferLocked: -> slot=%d/%" PRIu64,
item->mBuf, item->mFrameNumber);
return OK;
}
回到FramebufferSurface的onFrameAvailable中這樣獲取了buffer之後,呼叫了HWComposer的fbPost方法。
三、egl合成數據在HWComposer的處理
繼上面呼叫fbPost方法,我們來看下,這裡是呼叫了setFramebufferTarget方法。
int HWComposer::fbPost(int32_t id,
const sp<Fence>& acquireFence, const sp<GraphicBuffer>& buffer) {
if (mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) {
return setFramebufferTarget(id, acquireFence, buffer);
} else {
acquireFence->waitForever("HWComposer::fbPost");
return mFbDev->post(mFbDev, buffer->handle);
}
}
我們來看下setFramebufferTarget方法,這裡就是把該裝置的DisplayData資料中的framebufferTarget填充,主要是其handle資料,這裡就是egl合成好的資料buffer。
也就是最終egl合成好的資料放在DisplayData的framebufferTarget變數的handle中。
status_t HWComposer::setFramebufferTarget(int32_t id,
const sp<Fence>& acquireFence, const sp<GraphicBuffer>& buf) {
if (uint32_t(id)>31 || !mAllocatedDisplayIDs.hasBit(id)) {
return BAD_INDEX;
}
DisplayData& disp(mDisplayData[id]);
if (!disp.framebufferTarget) {
// this should never happen, but apparently eglCreateWindowSurface()
// triggers a Surface::queueBuffer() on some
// devices (!?) -- log and ignore.
ALOGE("HWComposer: framebufferTarget is null");
return NO_ERROR;
}
int acquireFenceFd = -1;
if (acquireFence->isValid()) {
acquireFenceFd = acquireFence->dup();
}
// ALOGD("fbPost: handle=%p, fence=%d", buf->handle, acquireFenceFd);
disp.fbTargetHandle = buf->handle;//egl合成好的資料
disp.framebufferTarget->handle = disp.fbTargetHandle;//egl合成好的資料,最終是放在這裡
disp.framebufferTarget->acquireFenceFd = acquireFenceFd;
return NO_ERROR;
}
四、硬體模組合成
這樣就剩最後一步了,把不管是普通layer的資料,還是egl合成好的資料傳送到硬體模組合成了,最後就到顯示裝置了。
繼第一節分析的doComposition函式最後會呼叫postFramebuffer函式,我們再來分析下這個函式,這個函式主要是呼叫了HWComposer的commit函式。
void SurfaceFlinger::postFramebuffer()
{
ATRACE_CALL();
const nsecs_t now = systemTime();
mDebugInSwapBuffers = now;
HWComposer& hwc(getHwComposer());
if (hwc.initCheck() == NO_ERROR) {
if (!hwc.supportsFramebufferTarget()) {
// EGL spec says:
// "surface must be bound to the calling thread's current context,
// for the current rendering API."
getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);
}
hwc.commit();
}
......
我們來看下HWComposer的commit函式,這個函式就是先設定了egl的那個裝置的surface和display,然後處理虛擬裝置的outbuf等,最後呼叫了硬體模組合成到顯示裝置上。status_t HWComposer::commit() {
int err = NO_ERROR;
if (mHwc) {
if (!hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) {
// On version 1.0, the OpenGL ES target surface is communicated
// by the (dpy, sur) fields and we are guaranteed to have only
// a single display.
mLists[0]->dpy = eglGetCurrentDisplay();//設定下egl相關變數
mLists[0]->sur = eglGetCurrentSurface(EGL_DRAW);
}
for (size_t i=VIRTUAL_DISPLAY_ID_BASE; i<mNumDisplays; i++) {
DisplayData& disp(mDisplayData[i]);
if (disp.outbufHandle) {//只有虛擬裝置需要設定outbuf
mLists[i]->outbuf = disp.outbufHandle;
mLists[i]->outbufAcquireFenceFd =
disp.outbufAcquireFence->dup();
}
}
err = mHwc->set(mHwc, mNumDisplays, mLists);//呼叫硬體模組合成
......
相關推薦
Android6.0 影象合成過程詳解(二) doComposition函式
上篇部落格分析到setUpHWComposer函式,這裡我們繼續分析影象合成的過程從doComposition函式開始,以及在這過程中解答一些上篇部落格提出的疑問。 一、doComposition合成圖層 doComposition這個函式就是合成所有層的影象 void
Android6.0 MountService和vold詳解(三) vold SD卡、otg
既上面兩篇部落格,繼續分析vold、對外接SD卡和OTG的分析: 一、process_config函式 上一篇我們再main函式中分析了VolumeManager的start函式,這次我們接下來分析process_config函式 static int process_c
rpm包打包過程詳解(二)——製作原始碼安裝包
製作原始碼安裝包(.tar.gz) 1. 解決依賴的軟體: 系統環境:[紅帽企業Linux.6.4.64位伺服器版].rhel-server-6.4-x86_64 原始碼製作中使用到的軟體為GNU M4,GNU autoconf,GNU automake;GNU
c/c++預處理過程詳解(二)之條件編譯及預定義的巨集
未經博主同意不得私自轉載!不準各種形式的貼上複製本文及盜圖! 首先對於上篇文章中巨集定義的補充: (1)#define NAME"zhangyuncong" 程式中有"NAME"則,它會不會被替換呢? (2)#define 0x abcd 可以嗎?也就是說,可不可以用不是
Mysql加鎖過程詳解(4)-select for update/lock in share mode 對事務並發性影響
per inno targe 允許 evel transacti 修改 not null warn select for update/lock in share mode 對事務並發性影響 事務並發性理解 事務並發性,粗略的理解就是單位時間內能夠執行的事務數量,常見的單
Mysql加鎖過程詳解(9)-innodb下的記錄鎖,間隙鎖,next-key鎖
ans 唯一索引 crazy cimage -h insert tran 存在 gin Mysql加鎖過程詳解(1)-基本知識 Mysql加鎖過程詳解(2)-關於mysql 幻讀理解 Mysql加鎖過程詳解(3)-關於mysql 幻讀理解 Mysql加鎖過程詳解(4)-
H 264的兩個概念 DC係數和AC係數 MV預測過程詳解(附圖)
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
30分鐘學會EventBus3.0詳解(一)(引入和初始化EventBus3.0) 30分鐘學會EventBus3.0詳解(二)(EventBus3.0的詳細使用) 30分鐘學會EventBus3.0詳解(一)(引入和初始化EventBus3.0) 30分鐘學會EventBus3.0詳解(二)(Ev
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
0 httpd2.2配置詳解-Apache配置檔案詳解-(二)
httpd-2.2 15 curl命令 curl是基於URL語法在命令列方式下工作的檔案傳輸工具,它支援FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE及LDAP等協議。curl支援HTTPS認證,並且支援HTTP的POST、PU
0 httpd2.2配置詳解-Apache配置文件詳解-(二)
切換 more 簡化 css 程序 ip地址 在服務器 filter utf httpd-2.2 15 curl命令 curl是基於URL語法在命令行方式下工作的文件傳輸工具,它支持FTP, FTPS, HTTP, HTTPS, GOPHER,
Android應用程式啟動詳解(二)從原始碼瞭解App的啟動過程
本文承接《Android應用程式啟動詳解(一)》繼續來學習應用程式的啟動的那些事。上文提到startActivity()方法啟動一個app後經過一翻過程就到了app的入口方法ActivityThread.main()。其實我們在之前的文章中《Android的訊息機制(二)之L
Java程式設計師從笨鳥到菜鳥之(一百零一)sql注入攻擊詳解(二)sql注入過程詳解
l 猜解資料庫中使用者名錶的名稱猜解法:此方法就是根據個人的經驗猜表名,一般來說,user,users,member,members,userlist,memberlist,userinfo,manager,admin,adminuser,systemuser,systemusers,sysuser,sysu
sql注入攻擊詳解(二)sql注入過程詳解
l 猜解資料庫中使用者名錶的名稱 猜解法:此方法就是根據個人的經驗猜表名,一般來說,user,users,member,members,userlist,memberlist,userinfo,manager,admin,adminuser,systemuser,systemusers,sysuser,sys
Linux 開機引導和啟動過程詳解(2)
理解作業系統開機引導和啟動過程對於配置作業系統和解決相關啟動問題是至關重要的。該文章陳述了 GRUB2 引導裝載程式開機引導裝載核心的過程和 systemd 初始化系統執行開機啟動作業系統的過程。 事實上,作業系統的啟動分為兩個階段:引導boot和啟動startup。引導
Mysql觸發器、檢視、儲存過程詳解(例項)
/*觸發器trigger*/觸發器的概念:監視某種情況並出發某種操作例如:一個電子商城商品表goods簡稱g:主鍵 商品名 庫存 1 電腦 282 手錶 120訂單表o:訂單主鍵 訂單外來鍵 購買數量1
Pentaho BI Server的啟動過程詳解(一)
最近在群裡大家都在討論Pentaho BI Server 的一些問題,但是大家對於Pentaho BI Server在啟動時都做了些什麼並不是十分了解,在這裡我就來和大家聊一聊這個過程。 軟體和版本 Pentaho BIServer 5.0.1-ce 幾個
VxWorks啟動過程詳解(上)
vxworks有三種映像: VxWorks Image的檔案型別有三種 Loadable Images:由Boot-ROM引導通過網口或串列埠下載到RAM ROM-based Images
MPAndroidChart3.0使用詳解(二)----柱狀圖、折線圖、組合圖的使用
上篇主要講到了MPAndroidChart這個開源庫的一些基本特性和基礎設定,現在來講下我們經常要用到的柱狀圖(直方圖)、折線圖和組合圖的使用。 柱狀圖 再講之前,先看效果圖。 第一個是單個柱狀圖,第二個是組合(group)的柱狀圖 使用 1、
java.util包詳解(二)——Connection接口
操作 相同 元素 叠代 cat roo soft true nbsp Connection接口介紹 Connection接口是java集合的root接口,沒有實現類,只有子接口和實現子接口的各種容器。主要用來表示java集合這一大的抽象概念。 Connection接
C++ 模板詳解(二)(轉)
創建 規則 error ++ 例如 public err iostream () 四、類模板的默認模板類型形參 1、可以為類模板的類型形參提供默認值,但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認值。 2、類模板的類型形