SurfaceFlinger圖像合成[1]

Layer接收到新的GraphicBuffer

void Layer::onFrameAvailable(const BufferItem& item) {
    // 將新的Buffer添加到Layer內部的mQueueItems隊列進行處理
    { 
        ....
        //將 BufferItem放入Layer隊列中
        mQueueItems.push_back(item);
        //Layer中待處理Buffer +1
        android_atomic_inc(&mQueuedFrames);

        // Wake up any pending callbacks
        mLastFrameNumberReceived = item.mFrameNumber;
        ....
    }
   //通知SurfaceFlinger進行更新
    mFlinger->signalLayerUpdate();
}

Layer中接收到其生產者產生的GraphicBuffer后會接收到onFrameAvailable的通知瘦癌,Layer將新的Buffer信息放入自己的mQueueItems隊列中等待合成,待合成的Buffer數(shù)量+1,最后調用signalLayerUpdate通知SurfaceFlinger合成.

void SurfaceFlinger::signalLayerUpdate() {
    mEventQueue.invalidate();
}

void MessageQueue::invalidate() {
    ......
    mEvents->requestNextVsync();
    ......
}

signalLayerUpdate方法調用了MessageQueue的invalidate方法肋拔,而invalidate又調用了requestNextVsync方法,這個方法在Vsync分析的時候已經了解過趋观,主要作用是通知Vsync機制在下一次SF的Vsync到來的時候喚醒SF進行工作. 而SF在接收到下次Vsync到來的時候便會執(zhí)行圖形合成.
再來看下SF接收到Vsync的處理流程

void SurfaceFlinger::onMessageReceived(int32_t what) {
    ATRACE_CALL();
    switch (what) {
        ......
        //Vsync到來執(zhí)行invalidate方法
        case MessageQueue::INVALIDATE: {
            bool refreshNeeded = handleMessageTransaction();
            refreshNeeded |= handleMessageInvalidate();
            refreshNeeded |= mRepaintEverything;
            if (refreshNeeded) {
                // 更加更新結果決定是否需要進行刷新屏幕顯示缭保,如果窗口狀態(tài)發(fā)生了變化,則需要重新合成并刷新屏幕顯示.
                signalRefresh();
            }
            break;
        }
        //合成UI刷新到屏幕
        case MessageQueue::REFRESH: {
            handleMessageRefresh();
            break;
        }
    }
}

SF圖形合成又大體上分為了兩個部分
1:SF更新合成相關的信息
2:SF執(zhí)行合成操作并顯示

SF根據(jù)更新結果來決定是否需要再次合成并顯示,

SF更新合成相關的信息

handleMessageTransaction

handleMessageTransaction主要處理Layer屬性變化蓖宦, 顯示設備變化等情況油猫,最終將變化的信息mCurrentState提交到mDrawingState, 等待合成處理.

handleMessageTransaction方法經過層層調用最終執(zhí)行到 handleTransactionLocked方法中稠茂,我們直接看這個方法.
SF有兩個狀態(tài) mDrawingState表示正在繪制顯示的狀態(tài),mCurrentState表示從上次 繪制后 發(fā)生變化的信息. 修改的信息都保存在mCurrentState中情妖,等到修改完成后統(tǒng)一提交到mDrawingState中睬关。

void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
{
    //獲取mCurrentState中所有的Layer。
    const LayerVector& currentLayers(mCurrentState.layersSortedByZ);
    const size_t count = currentLayers.size();

    //遍歷mCurrentState中所有的Layer,對發(fā)生變化的Layer執(zhí)行doTransaction函數(shù)鲫售,判斷Layer的可見區(qū)域是否發(fā)生了變化
    if (transactionFlags & eTraversalNeeded) {
        for (size_t i=0 ; i<count ; i++) {
            const sp<Layer>& layer(currentLayers[i]);
            //根據(jù)eTransactionNeeded,判斷Layer是否發(fā)生了變化共螺,Layer發(fā)生變化后會設置這個flag
            uint32_t trFlags = layer->getTransactionFlags(eTransactionNeeded);
            if (!trFlags) continue;

            //Layer的doTransaction函數(shù)會會對比Layer的舊的狀態(tài)和新的狀態(tài)是否發(fā)生變化,當Layer的可見區(qū)域大小發(fā)生變化之后情竹,設置mVisibleRegionsDirty為true
            const uint32_t flags = layer->doTransaction(0);
            if (flags & Layer::eVisibleRegion)
                mVisibleRegionsDirty = true;
        }
    }

handleTransactionLocked第一部分:檢查Layer VisibleRegion是否發(fā)生變化.
遍歷所有的Layer查看SF的visibleRegion是否發(fā)生了變化藐不,首先會先檢查Layer是否發(fā)生了變化, Layer沒有發(fā)生變化則不需要檢查該Layer,如果Layer發(fā)生了變化秦效,需要對比該Layer的顯示區(qū)域和原來顯示區(qū)域是否發(fā)生變化雏蛮,若變化設置mVisibleRegionsDirty為true。

    //遍歷并檢查所有的顯示設備阱州,檢查顯示設備是否發(fā)生了增加或者減少.并做相應的處理
    if (transactionFlags & eDisplayTransactionNeeded) {
        //兩個列表來保存上次合成時顯示設備的信息和當前顯示設備的信息
        // draw 上次合成時顯示設備信息
        // curr 當前顯示設備的信息
        const KeyedVector<  wp<IBinder>, DisplayDeviceState>& curr(mCurrentState.displays);
        const KeyedVector<  wp<IBinder>, DisplayDeviceState>& draw(mDrawingState.displays);
        //判斷是否發(fā)生變化
        if (!curr.isIdenticalTo(draw)) {
            mVisibleRegionsDirty = true;
            const size_t cc = curr.size();
                  size_t dc = draw.size();

            // 找到刪除的顯示設備信息
            // (ie: 在draw列表中挑秉,但是卻不在curr列表中,說明有設備刪除)
            // 處理顯示設備發(fā)生變化
            // (ie: 兩個列表中都有該顯示設備)
            for (size_t i=0 ; i<dc ; i++) {
                const ssize_t j = curr.indexOfKey(draw.keyAt(i));
                if (j < 0) {
                    // 1: 顯示設備移除: 在draw列表中苔货,卻不在curr列表中
                    if (!draw[i].isMainDisplay()) {
                        // 如果有設備移除犀概,切當前設備不是主顯示設備的時候,將EGL的Context設置到默認的主顯示設備
                        // 從HWC斷開該設備夜惭,通知EventThread設備熱插拔事件姻灶,從Display列表移除
                        const sp<const DisplayDevice> defaultDisplay(getDefaultDisplayDevice());
                        defaultDisplay->makeCurrent(mEGLDisplay, mEGLContext);
                        sp<DisplayDevice> hw(getDisplayDevice(draw.keyAt(i)));
                        if (hw != NULL)
                            hw->disconnect(getHwComposer());
                        if (draw[i].type < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES)
                            mEventThread->onHotplugReceived(draw[i].type, false);
                        mDisplays.removeItem(draw.keyAt(i));
                    } else {
                        // 主設備不允許移除,打印Warnning 提示信息
                        ALOGW("trying to remove the main display");
                    }
                } else {
                    // 設備在兩個列表中都有诈茧,但是有信息發(fā)生了變化
                    const DisplayDeviceState& state(curr[j]);
                    const wp<IBinder>& display(curr.keyAt(j));
                    const sp<IBinder> state_binder = IInterface::asBinder(state.surface);
                    const sp<IBinder> draw_binder = IInterface::asBinder(draw[i].surface);
                    if (state_binder != draw_binder) {
                        // changing the surface is like destroying and
                        // recreating the DisplayDevice, so we just remove it
                        // from the drawing state, so that it get re-added
                        // below.
                        sp<DisplayDevice> hw(getDisplayDevice(display));
                        if (hw != NULL)
                            hw->disconnect(getHwComposer());
                        mDisplays.removeItem(display);
                        mDrawingState.displays.removeItemsAt(i);
                        dc--; i--;
                        // at this point we must loop to the next item
                        continue;
                    }
                    //更新顯示設備信息
                    const sp<DisplayDevice> disp(getDisplayDevice(display));
                    if (disp != NULL) {
                        if (state.layerStack != draw[i].layerStack) {
                            disp->setLayerStack(state.layerStack);
                        }
                        if ((state.orientation != draw[i].orientation)
                                || (state.viewport != draw[i].viewport)
                                || (state.frame != draw[i].frame))
                        {
                            disp->setProjection(state.orientation,
                                    state.viewport, state.frame);
                        }
                        if (state.width != draw[i].width || state.height != draw[i].height) {
                            disp->setDisplaySize(state.width, state.height);
                        }
                    }
                }
            }

            // 找到新添加的Display設備信息
            // (ie: draw列表中沒有产喉,curr列表中有該設備,說明有新設備添加進來)
            for (size_t i=0 ; i<cc ; i++) {
                if (draw.indexOfKey(curr.keyAt(i)) < 0) {
                    const DisplayDeviceState& state(curr[i]);

                    sp<DisplaySurface> dispSurface;
                    sp<IGraphicBufferProducer> producer;
                    sp<IGraphicBufferProducer> bqProducer;
                    sp<IGraphicBufferConsumer> bqConsumer;
                    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer,
                            new GraphicBufferAlloc());

                    int32_t hwcDisplayId = -1;
                    if (state.isVirtualDisplay()) {
                        // 虛擬顯示設備暫時不關注
                        if (state.surface != NULL) {

                            int width = 0;
                            int status = state.surface->query(
                                    NATIVE_WINDOW_WIDTH, &width);
                            ALOGE_IF(status != NO_ERROR,
                                    "Unable to query width (%d)", status);
                            int height = 0;
                            status = state.surface->query(
                                    NATIVE_WINDOW_HEIGHT, &height);
                            ALOGE_IF(status != NO_ERROR,
                                    "Unable to query height (%d)", status);
                            if (MAX_VIRTUAL_DISPLAY_DIMENSION == 0 ||
                                    (width <= MAX_VIRTUAL_DISPLAY_DIMENSION &&
                                     height <= MAX_VIRTUAL_DISPLAY_DIMENSION)) {
                                hwcDisplayId = allocateHwcDisplayId(state.type);
                            }

                            sp<VirtualDisplaySurface> vds = new VirtualDisplaySurface(
                                    *mHwc, hwcDisplayId, state.surface,
                                    bqProducer, bqConsumer, state.displayName);

                            dispSurface = vds;
                            producer = vds;
                        }
                    } else {
                        //為新添加的設備分配HWC ID,并創(chuàng)建FramebufferSurface消費者.
                        ALOGE_IF(state.surface!=NULL,
                                "adding a supported display, but rendering "
                                "surface is provided (%p), ignoring it",
                                state.surface.get());
                        hwcDisplayId = allocateHwcDisplayId(state.type);
                        dispSurface = new FramebufferSurface(*mHwc, state.type,
                                bqConsumer);
                        producer = bqProducer;
                    }

                    const wp<IBinder>& display(curr.keyAt(i));
                    if (dispSurface != NULL) {
                        //創(chuàng)建該顯示設備的DisplayDevice敢会,并添加到mDisplays中.
                        sp<DisplayDevice> hw = new DisplayDevice(this,
                                state.type, hwcDisplayId,
                                mHwc->getFormat(hwcDisplayId), state.isSecure,
                                display, dispSurface, producer,
                                mRenderEngine->getEGLConfig());
                        hw->setLayerStack(state.layerStack);
                        hw->setProjection(state.orientation,
                                state.viewport, state.frame);
                        hw->setDisplayName(state.displayName);
                        mDisplays.add(display, hw);
                        if (state.isVirtualDisplay()) {
                            if (hwcDisplayId >= 0) {
                                mHwc->setVirtualDisplayProperties(hwcDisplayId,
                                        hw->getWidth(), hw->getHeight(),
                                        hw->getFormat());
                            }
                        } else {
                            // 通知EventThread有新設備熱插拔
                            mEventThread->onHotplugReceived(state.type, true);
                        }
                    }
                }
            }
        }
    }

handleTransactionLocked第二部分:檢查顯示設備是否發(fā)生變化
1:檢查Display是否移除曾沈,則更新SF移除相關的信息
2:檢查Display是否發(fā)生變化, 若發(fā)生變化則需要將Display相關的信息更新為新的顯示設備信息
3:檢查Display是否添加新設備鸥昏,若添加新設備塞俱,則按照設備添加流程為新設備創(chuàng)建BufferQueue和FrameBufferSurface等.

    //當顯示設備發(fā)生變化或者某些Layer發(fā)生變化后,需要修改Layer的transform hint, 使Layer視圖的矩陣變化互广。
    //遍歷mCurrentState所有的Layer并更新他的transform hint
    //如果一個Layer只顯示在一個顯示設備上敛腌,那我們就用這個顯示設備來計算他的Transform hint,如果顯示在多個顯示設備上卧土,則需要按照默認主顯示設備計算.
    if (transactionFlags & (eTraversalNeeded|eDisplayTransactionNeeded)) {
        
        sp<const DisplayDevice> disp;
        uint32_t currentlayerStack = 0;
        for (size_t i=0; i<count; i++) {
            // NOTE: we rely on the fact that layers are sorted by
            // layerStack first (so we don't have to traverse the list
            // of displays for every layer).
            const sp<Layer>& layer(currentLayers[i]);
            uint32_t layerStack = layer->getDrawingState().layerStack;
            if (i==0 || currentlayerStack != layerStack) {
                currentlayerStack = layerStack;
                //根據(jù)當前Layer的layerStack找到所屬的顯示設備Display
                disp.clear();
                for (size_t dpy=0 ; dpy<mDisplays.size() ; dpy++) {
                    sp<const DisplayDevice> hw(mDisplays[dpy]);
                    if (hw->getLayerStack() == currentlayerStack) {
                        if (disp == NULL) {
                            disp = hw;
                        } else {
                            disp = NULL;
                            break;
                        }
                    }
                }
            }
            if (disp == NULL) {
                //如果顯示在過個顯示設備上,則使用默認顯示設備
                disp = getDefaultDisplayDevice();
            }
            //將顯示設備信息更新到Layer中
            layer->updateTransformHint(disp);
        }
    }

handleTransactionLocked第三部分:更新transform hint相關信息
當顯示設備發(fā)生變化或者某些Layer發(fā)生變化后像樊,需要修改Layer的transform hint.

    
    //如有有新添加的Layer尤莺, 則mVisibleRegionsDirty = true
    const LayerVector& layers(mDrawingState.layersSortedByZ);
    if (currentLayers.size() > layers.size()) {
        // layers have been added
        mVisibleRegionsDirty = true;
    }

    // 如果有Layer移除, 該Layer原先的顯示區(qū)域就是需要更新顯示區(qū)域
    if (mLayersRemoved) {
        mLayersRemoved = false;
        mVisibleRegionsDirty = true;
        const size_t count = layers.size();
        for (size_t i=0 ; i<count ; i++) {
            const sp<Layer>& layer(layers[i]);
            if (currentLayers.indexOf(layer) < 0) {
                const Layer::State& s(layer->getDrawingState());
                Region visibleReg = s.transform.transform(
                        Region(Rect(s.active.w, s.active.h)));
                invalidateLayerStack(s.layerStack, visibleReg);
            }
        }
    }

    commitTransaction();

}

handleTransactionLocked第四部分: 更新Layer信息
若有Layer移除生棍,更新SF的visibleRegion颤霎, 說明原來該Layer顯示的區(qū)域需要重新刷新顯示
最后調用commitTransaction,提交mCurrentState信息, 將所有變化的信息更新完成后涂滴,mCurrentState提交到mDrawingState中. 下一步就要合成顯示這些變化后的內容了.

handleMessageInvalidate

handleMessageInvalidate會調用handlePageFlip函數(shù). 直接看handlePageFlip的邏輯

handlePageFlip

bool SurfaceFlinger::handlePageFlip()
{
    Region dirtyRegion;

    bool visibleRegions = false;
    //拿到mDrawingState中的所有Layer, 就是我們上一步中剛提交的變化的Layer
    const LayerVector& layers(mDrawingState.layersSortedByZ);
    bool frameQueued = false;

    //遍歷所有的Layer, 找到有新的Buffer到來需要合成的Layer,將這些Layer添加到layersWithQueuedFrames列表中.
    //那些Layer需要合成呢友酱?
    // 1:Layer中有新的待處理的Buffer
    // 2:Buffer需要立刻顯示的。(未到指定顯示時間的不需要處理)
    Vector<Layer*> layersWithQueuedFrames;
    for (size_t i = 0, count = layers.size(); i<count ; i++) {
        const sp<Layer>& layer(layers[i]);
        if (layer->hasQueuedFrame()) {
            frameQueued = true;
            if (layer->shouldPresentNow(mPrimaryDispSync)) {
                layersWithQueuedFrames.push_back(layer.get());
            } else {
                layer->useEmptyDamage();
            }
        } else {
            layer->useEmptyDamage();
        }
    }
    //遍歷所有需要處理的Layer柔纵,并從BufferQueue中獲取Buffer并更新缔杉,最終更新到Display的visibleRegion中
    for (size_t i = 0, count = layersWithQueuedFrames.size() ; i<count ; i++) {
        Layer* layer = layersWithQueuedFrames[i];
        //latchBuffer是在BufferQueue中分析過,從BufferQueue中獲取Buffer,并將Buffer綁定到Layer對應的紋理上.
        const Region dirty(layer->latchBuffer(visibleRegions));
        layer->useSurfaceDamage();
        const Layer::State& s(layer->getDrawingState());
        //將Layer的visibleRegion 更新到對應顯示設備的Display上.
        invalidateLayerStack(s.layerStack, dirty);
    }

    mVisibleRegionsDirty |= visibleRegions;

    // Layer有待處理的Buffer,但是不到顯示時間搁料,則設置下一個Vsync到來的時候喚醒SF進行處理.
    if (frameQueued && layersWithQueuedFrames.empty()) {
        signalLayerUpdate();
    }

    // 如果有需要處理的Layer, layersWithQueuedFrames中有數(shù)據(jù)或详,則需要刷新合成
    return !layersWithQueuedFrames.empty();
}

handlePageFlip方法主要來處理Layer Buffer相關的內容.
1: 找到此次Vsync事件中有需要處理的Buffer的Layer。
2: 遍歷所有需要待處理的Layer, 從BufferQueue中拿出該Layer對應的Buffer,更新到Layer對應的紋理中, 計算Layer的visible區(qū)域郭计,更新到對應顯示設備的DisplayDevice上霸琴。
3: 判斷Layer是否有待處理但是還沒有到指定事件的Buffer, 如果有則需要下次Vsync事件在處理,調用signalLayerUpdate預約下次Vsync事件.
4: 返回結果:如果此次Vsync有要處理的Layer,則說明需要重新合成昭伸,返回結果為true梧乘。

總結

此時invalidate就基本就分析完了.總結下

    bool refreshNeeded = handleMessageTransaction();
    refreshNeeded |= handleMessageInvalidate();
    refreshNeeded |= mRepaintEverything;
    if (refreshNeeded) {
        // Signal a refresh if a transaction modified the window state,
        // a new buffer was latched, or if HWC has requested a full
        // repaint
        signalRefresh();
    }

Vsync到來時,INVALIDATE過程主要處理了 SF中距上次合成后的一些變化信息庐杨。
1:handleMessageTransaction 處理了Layer屬性變化选调, 顯示設備變化等情況,然后將變化的信息mCurrentState提交到了mDrawingState灵份,等待合成處理.
主要包括Layer屬性發(fā)生變化学歧,顯示設備添加和移除的處理,更新顯示設備的transform hint相關的信息各吨,處理Layer移除和增加等相關的信息,如果這些信息發(fā)生變化袁铐, 則需要重新合成刷新顯示揭蜒。
2:handleMessageInvalidate 更新了Layer的buffer內容到Layer的紋理
找到有待處理Buffer的Layer, 將Layer的buffer從BufferQueue中拿到,更新到紋理中. 如果內容發(fā)生變化剔桨,需要重新刷新合成.
3:mRepaintEverything 表示HWC硬件要求強制刷新

當以上過程中有對應的條件發(fā)生了變化屉更,就會調用signalRefresh,通知SF進行合成刷新.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末洒缀,一起剝皮案震驚了整個濱河市瑰谜,隨后出現(xiàn)的幾起案子欺冀,更是在濱河造成了極大的恐慌,老刑警劉巖萨脑,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隐轩,死亡現(xiàn)場離奇詭異,居然都是意外死亡渤早,警方通過查閱死者的電腦和手機职车,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹊杖,“玉大人悴灵,你說我怎么就攤上這事÷畋停” “怎么了积瞒?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長登下。 經常有香客問我茫孔,道長,這世上最難降的妖魔是什么庐船? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任银酬,我火速辦了婚禮,結果婚禮上筐钟,老公的妹妹穿的比我還像新娘揩瞪。我一直安慰自己篓冲,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布嗤攻。 她就那樣靜靜地躺著诽俯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暴区。 梳的紋絲不亂的頭發(fā)上闯团,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音仙粱,去河邊找鬼房交。 笑死,一個胖子當著我的面吹牛伐割,可吹牛的內容都是我干的候味。 我是一名探鬼主播刃唤,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼白群!你這毒婦竟也來了尚胞?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤川抡,失蹤者是張志新(化名)和其女友劉穎辐真,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崖堤,經...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡侍咱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了密幔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楔脯。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胯甩,靈堂內的尸體忽然破棺而出昧廷,到底是詐尸還是另有隱情,我是刑警寧澤偎箫,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布木柬,位于F島的核電站,受9級特大地震影響淹办,放射性物質發(fā)生泄漏眉枕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一速挑、第九天 我趴在偏房一處隱蔽的房頂上張望姥宝。 院中可真熱鬧恐疲,春花似錦、人聲如沸培己。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至壁公,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間紊册,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工芳绩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妥色,地道東北人遏片。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像笔呀,于是被迫代替她去往敵國和親髓需。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354