硬件渲染-數(shù)據(jù)同步


喚醒渲染線程

Java層,ThreadedRenderer的draw方法俊鱼,首先記錄樹形結(jié)構每個視圖節(jié)點繪制操作,在底層DisplayListData保存,然后JNI的#nSyncAndDrawFrame方法實現(xiàn)同步忽妒。
根據(jù)指針找到底層RenderProxy代理對象。RenderProxy的#syncAndDrawFrame方法兼贸。

int RenderProxy::syncAndDrawFrame() {
    return mDrawFrameTask.drawFrame();
}

DrawFrameTask任務段直,繪制幀

int DrawFrameTask::drawFrame() {
    mSyncResult = kSync_OK;
    mSyncQueued = systemTime(CLOCK_MONOTONIC);
    postAndWait();
    return mSyncResult;
}

postAndWait方法,讓主線程等待溶诞,通過Singal的#wait方法坷牛,在幀繪制任務中,會調(diào)用unblockUiThread方法很澄,喚醒主線程。

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    mRenderThread->queue(this);
    mSignal.wait(mLock);
}

RenderThread繼承Thread颜及,單例甩苛,內(nèi)部有一個任務隊列TaskQueue,queue方法俏站,將該幀繪制任務DrawFrameTask加入隊列讯蒲。

void RenderThread::queue(RenderTask* task) {
    AutoMutex _lock(mLock);
    mQueue.queue(task);
    if (mNextWakeup && task->mRunAt < mNextWakeup) {
        mNextWakeup = 0;
        mLooper->wake();
    }
}

幀繪制任務DrawFrameTask,繼承RenderTask肄扎,插入隊列TaskQueue墨林,判斷下次喚醒的時間,喚醒渲染線程犯祠。渲染線程通過Looper機制休眠旭等。

RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
    ... {
    mFrameCallbackTask = new DispatchFrameCallbacks(this);
    mLooper = new Looper(false);
    run("RenderThread");
}

RenderThread構造方法,創(chuàng)建一個Looper衡载,線程啟動搔耕,threadLoop循環(huán)。

bool RenderThread::threadLoop() {
    setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
    initThreadLocals();
    int timeoutMillis = -1;
    for (;;) {//循環(huán)
        int result = mLooper->pollOnce(timeoutMillis);
        nsecs_t nextWakeup;
        while (RenderTask* task = nextTask(&nextWakeup)) {
            task->run();//喚醒后痰娱,隊列順序執(zhí)行
        }
        if (nextWakeup == LLONG_MAX) {
            timeoutMillis = -1;
        } else {
            //設置下一次自動喚醒的時間
            nsecs_t timeoutNanos = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = nanoseconds_to_milliseconds(timeoutNanos);
            if (timeoutMillis < 0) {
                timeoutMillis = 0;
            }
        }
        ...
    }
    return false;
}

渲染線程在Looper的#pollOnce方法處無限期休眠弃榨,主線程向隊列插入幀渲染任務時,Looper的#wake方法喚醒渲染線程梨睁,隊列的RenderTask任務依次執(zhí)行鲸睛。


渲染任務

渲染線程,兩個功能坡贺。

1官辈,同步幀狀態(tài)箱舞,syncFrameState方法。
2钧萍,繪制褐缠,draw方法。

void DrawFrameTask::run() {
    bool canUnblockUiThread;
    bool canDrawThisFrame;
    {
        TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
        canUnblockUiThread = syncFrameState(info);
        canDrawThisFrame = info.out.canDrawThisFrame;
    }
    //任務內(nèi)部的CanvasContext
    CanvasContext* context = mContext;
    if (canUnblockUiThread) {//喚醒主線程
        unblockUiThread();
    }
    if (CC_LIKELY(canDrawThisFrame)) {
        context->draw();
    }
    if (!canUnblockUiThread) {
        unblockUiThread();
    }
}

一次硬件渲染任務整流程圖风瘦。

一次硬件渲染任務整流程圖

同步幀狀態(tài)

一個任務队魏,就是一幀數(shù)據(jù),syncFrameState方法万搔,同步幀狀態(tài)和數(shù)據(jù)胡桨。

bool DrawFrameTask::syncFrameState(TreeInfo& info) {
    int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
    mRenderThread->timeLord().vsyncReceived(vsync);
    mContext->makeCurrent();
    Caches::getInstance().textureCache.resetMarkInUse(mContext);
    //第一步,遍歷DeferredLayerUpdater數(shù)組Vector
    //觸發(fā)CanvasContext的processLayerUpdate方法瞬雹。
    for (size_t i = 0; i < mLayers.size(); i++) {
        mContext->processLayerUpdate(mLayers[i].get());
    }
    //每次push結(jié)束后清理數(shù)組
    mLayers.clear();
    //第二步
    mContext->prepareTree(info, mFrameInfo, mSyncQueued);
    if (info.out.hasAnimations) {
        if (info.out.requiresUiRedraw) {
            mSyncResult |= kSync_UIRedrawRequired;
        }
    }
    //如果返回的是false昧谊,說明我們耗盡了textureCache的空間
    return info.prepareTextures;
}
Layers數(shù)組分析

分析一下什么樣的視圖會將Layer加入DrawFrameTask的Layers數(shù)組?

DrawFrameTask的#pushLayerUpdate方法酗捌,負責向數(shù)組添加元素呢诬,追溯到Java層。ThreadedRenderer的#pushLayerUpdate方法胖缤。

@Override
void pushLayerUpdate(HardwareLayer layer) {
    nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}

通過JNI#方法尚镰,觸發(fā)RenderProxy的#pushLayerUpdate方法。

void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
    mDrawFrameTask.pushLayerUpdate(layer);
}

因此哪廓,由Java層ThreadedRenderer的pushLayerUpdate方法發(fā)起狗唉,向Layers數(shù)組添加元素,是DeferredLayerUpdater類型的Layer涡真。那么分俯,這是什么樣的視圖呢?
下面是兩個HardwareLayer#方法哆料。

public void setSurfaceTexture(SurfaceTexture surface) {
    nSetSurfaceTexture(mFinalizer.get(), surface, false);
    mRenderer.pushLayerUpdate(this);//mRenderer即ThreadedRenderer缸剪。
}

public void updateSurfaceTexture() {
    nUpdateSurfaceTexture(mFinalizer.get());
    mRenderer.pushLayerUpdate(this);
}

都觸發(fā)ThreadedRenderer的#pushLayerUpdate方法,TextureView視圖有一個HardwareLayer东亦,對應底層DeferredLayerUpdater橄登,封裝真正的Layer。
HardwareLayer結(jié)構圖讥此。

HardwareLayer結(jié)構圖

TextureView視圖繪制時拢锹,利用ThreadedRenderer將HardwareLayer底層DeferredLayerUpdater加入DrawFrameTask的Layer數(shù)組。遍歷數(shù)組針對TextureView視圖萄喳。對于普通視圖卒稳,數(shù)組是空的。

針對TextureView視圖處理

遍歷每一個DeferredLayerUpdater他巨,CanvasContext#processLayerUpdate方法充坑。

void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
    bool success = layerUpdater->apply();
    if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
        //mCanvas是OpenGLRenderer减江。
        mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
    }
}

將deferredUpdateScheduled標志的Layer加入OpenGLRenderer的Layer數(shù)組。
此標志從哪里來呢捻爷?TextureView視圖的Layer有此標志嗎辈灼?

View視圖有三種LayerType。
LAYER_TYPE_NONE也榄,
LAYER_TYPE_SOFTWARE巡莹,
LAYER_TYPE_HARDWARE。
底層對應None = 0甜紫,Software = 1降宅,RenderLayer = 2。
View的#setLayerType方法設置LayerType囚霸。
TextureView忽略LayerType類型腰根,默認是LAYER_TYPE_HARDWARE。

從源碼中可知拓型,Layer#updateDeferred方法設置deferredUpdateScheduled標志额嘿,Layer渲染后重置。此方法僅在RenderNode#pushLayerUpdate中調(diào)用劣挫,由RenderNode內(nèi)部Layer觸發(fā)岩睁。
LayerType是RenderLayer。

因此:上層專門設置LAYER_TYPE_HARDWARE的視圖才會在RenderNode內(nèi)部創(chuàng)建Layer揣云,并有此標志。目前看來冰啃,TextureView視圖底層Layer沒有deferredUpdateScheduled標志邓夕,不會被加入OpenGLRenderer的Layer數(shù)組。

總結(jié)

第一步阎毅,處理和TextureView視圖相關焚刚,底層Layer,普通視圖無HardwareLayer扇调,因此矿咕,DrawFrameTask數(shù)組是空。

第二步 準備ViewTree的數(shù)據(jù)
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, 
                int64_t syncQueued) {
    mRenderThread.removeFrameCallback(this);
    //當前幀F(xiàn)rameInfo無SkippedFrame標志
    //說明之前的幀沒有被跳過狼钮,mCurrentFrameInfo指向緩沖區(qū)新節(jié)點碳柱。
    if (!wasSkipped(mCurrentFrameInfo)) {
        mCurrentFrameInfo = &mFrames.next();//RingBuffer<FrameInfo, 120>循環(huán)緩沖區(qū)
    }
    //若之前的幀被跳過,繼續(xù)用當前指向的節(jié)點進行初始化熬芜。
    mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
    mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
    mCurrentFrameInfo->markSyncStart();
    //初始化info信息
    info.damageAccumulator = &mDamageAccumulator;
    info.renderer = mCanvas;
    info.canvasContext = this;
    mAnimationContext->startFrame(info.mode);
    //底層根節(jié)點RenderNode準備莲镣,樹形結(jié)構遍歷
    mRootRenderNode->prepareTree(info);
    mAnimationContext->runRemainingAnimations(info);
    //不存在mNativeWindow退出,無法繪制該幀canDrawThisFrame設false
    int runningBehind = 0;
    mNativeWindow->query(mNativeWindow.get(),
            NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
    info.out.canDrawThisFrame = !runningBehind;
    if (!info.out.canDrawThisFrame) {
        mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
    }
    if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
        if (!info.out.requiresUiRedraw) {
            mRenderThread.postFrameCallback(this);
        }
    }
}

CanvasContext的底層根節(jié)點RootRenderNode涎拉,在CanvasContext構造方法初始化瑞侮。在上層ThreadedRenderer保存指針的圆,RootRenderNode繼承RenderNode。

void RenderNode::prepareTree(TreeInfo& info) {
    bool functorsNeedLayer = Properties::debugOverdraw;
    prepareTreeImpl(info, functorsNeedLayer);
}

從根節(jié)點開始半火,遞歸遍歷越妈。

void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
    info.damageAccumulator->pushTransform(this);
    //還有一種模式MODE_RT_ONLY
    if (info.mode == TreeInfo::MODE_FULL) {
        //屬性改變,賦值給mProperties钮糖。
        pushStagingPropertiesChanges(info);
    }
    bool willHaveFunctor = false;
    if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayListData) {
        willHaveFunctor = !mStagingDisplayListData->functors.isEmpty();
    } else if (mDisplayListData) {
        willHaveFunctor = !mDisplayListData->functors.isEmpty();
    }
    bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
            willHaveFunctor, functorsNeedLayer);
    prepareLayer(info, animatorDirtyMask);
    if (info.mode == TreeInfo::MODE_FULL) {
        //賦值給mDisplayListData梅掠。
        pushStagingDisplayListChanges(info);
    }
    prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData);
    pushLayerUpdate(info);

    info.damageAccumulator->popTransform();
}

prepareTree準備樹形結(jié)構的流程如下圖所示。

CanvasContext的prepareTree準備樹形結(jié)構的流程.jpg
關注兩個方法瓤檐。
prepareSubTree方法,遞歸遍歷子節(jié)點挠蛉。
pushLayerUpdate方法,RenderNode節(jié)點的內(nèi)部Layer處理谴古。

void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, 
                DisplayListData* subtree) {
    //該節(jié)點的DisplayListData存在
    if (subtree) {
        //獲取TextureCahe對象
        TextureCache& cache = Caches::getInstance().textureCache;
        //數(shù)據(jù)中Functor集合
        info.out.hasFunctors |= subtree->functors.size();
        for (size_t i = 0; info.prepareTextures && i < 
                        subtree->bitmapResources.size(); i++) {
            //若有一個texture失敗,是0稠歉,賦值prepareTextures,不再遍歷怒炸。
            info.prepareTextures = cache.prefetchAndMarkInUse(
                    info.canvasContext, subtree->bitmapResources[i]);
        }
        for (size_t i = 0; i < subtree->children().size(); i++) {
            DrawRenderNodeOp* op = subtree->children()[i];
            RenderNode* childNode = op->mRenderNode;
            info.damageAccumulator->pushTransform(&op->mTransformFromParent);
            bool childFunctorsNeedLayer = functorsNeedLayer
                    || op->mRecordedWithPotentialStencilClip;
            childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
            info.damageAccumulator->popTransform();
        }
    }
}
  • 遍歷SkBitmap數(shù)組,查看每個Bitmap是否準備好紋理阅羹。

SkBitmap數(shù)組元素從哪里來呢勺疼?

畫布繪制Bitmap,DisplayListCanvas#drawBitmap方法捏鱼。

void DisplayListCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
    //refBitmap會向mDisplayListData的bitmapResources數(shù)組中保存一份localBitmap
    bitmap = refBitmap(*bitmap); 
    paint = refPaint(paint);
    addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint));
}
inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
    SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
    alloc().autoDestroy(localBitmap);
    mDisplayListData->bitmapResources.push_back(localBitmap);
    return localBitmap;
}

增加一個DrawBitmapOp操作,向數(shù)組保存一個SkBitmap轨淌。

  • prefetchAndMarkInUse方法,返回Bitmap的Texture递鹉。
    根據(jù)piexlRef的stableId在LruCache緩存查找Texture藏斩,若未查到,判斷是否可創(chuàng)建灾茁,若加上Bitmap大小后大于緩存設計的大小谷炸,刪除未使用的舊Texture禀挫,若仍大于最大值局服,無法創(chuàng)建新Texture,返回null套硼。
    創(chuàng)建Texture時衫画,先glGenTextures,生成紋理對象索引缠导,保存在texture的id,bindTexture綁定僻造,glTexImage2D生成2D紋理將綁定Bitmap像素地址。最后竹挡,新Texture存入LruCache緩存立膛。
bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap) {
    Texture* texture = getCachedTexture(bitmap, AtlasUsageType::Use);
    if (texture) {//texture不為0
        texture->isInUse = ownerToken;
    }
    return texture;
}

若所有Texture存在揪罕,在syncFrameState結(jié)束后宝泵,返回info.prepareTextures標志true,它會影響喚醒主線程的時機。

  • 遍歷DisplayListData的DrawRenderNodeOp數(shù)組罢坝。底層子RenderNode節(jié)點。遞歸prepareTreeImpl方法嘁酿。

DrawRenderNodeOp數(shù)組是如何加入的呢?

追溯到Java層娱仔,DisplayListCanvas#drawRenderNode方法將子節(jié)點RenderNode寫入游桩。底層創(chuàng)建一個DrawRenderNodeOp操作耐朴,封裝被繪制視圖底層RenderNode節(jié)點盹憎,將操作加入到DrawRenderNodeOp數(shù)組筛峭。

RenderNode#pushLayerUpdate方法陪每。

void RenderNode::pushLayerUpdate(TreeInfo& info) {
    LayerType layerType = properties().effectiveLayerType();
    if (CC_LIKELY(layerType != LayerType::RenderLayer) || 
                CC_UNLIKELY(!isRenderable())) {
        if (CC_UNLIKELY(mLayer)) {
            LayerRenderer::destroyLayer(mLayer);
            mLayer = nullptr;
        }
        return;
    }
    bool transformUpdateNeeded = false;
    if (!mLayer) {
        //內(nèi)部Layer不存在,由LayerRenderer創(chuàng)建挂签。
        mLayer = LayerRenderer::createRenderLayer(info.renderState, 
                getWidth(), getHeight());
        applyLayerPropertiesToLayer(info);
        damageSelf(info);
        transformUpdateNeeded = true;
    }
    ...
    if (dirty.intersect(0, 0, getWidth(), getHeight())) {
        dirty.roundOut(&dirty);
        //初始化Layer的RenderNode盼产,deferredUpdateScheduled標志
        //初始化渲染器render為LayerRenderer。
        mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, 
                dirty.fRight, dirty.fBottom);
    }
    if (info.renderer && mLayer->deferredUpdateScheduled) {
        info.renderer->pushLayerUpdate(mLayer);//push進數(shù)組
    }
    ...
}

滿足兩個條件執(zhí)行辆飘。
LayerType必須是RenderLayer類型(Java層,View#setLayerType方法設置)蜈项。
DisplayListData不是空。
否則退出方法侥衬。
初始化創(chuàng)建RenderNode內(nèi)部Layer跑芳,TreeInfo的OpenGLRenderer,將該Layer加入OpenGLRenderer的Layer數(shù)組博个,等待OpenGLRenderer#updateLayers更新。

底層RenderNode結(jié)構圖如下盆佣。
底層RenderNode結(jié)構圖.jpg

總結(jié)

1共耍,底層根節(jié)點RootRenderNode開始虑灰,執(zhí)行prepareTreeImpl方法痹兜,prepareSubTree進入子視圖,從當前RenderNode的DisplayListData中查找子視圖RenderNode節(jié)點对湃,遞歸底層子視圖節(jié)點RenderNode的prepareTreeImpl方法崖叫。
2熟尉,底層根節(jié)點RootRenderNode并非頂層視圖節(jié)點,而是上層ThreadedRenderer內(nèi)部RenderNode對應的底層節(jié)點剧包。
3往果,上層畫布觸發(fā)過drawBitmap時疆液,存在SkBitmap數(shù)組陕贮。
4,上層設置LAYER_TYPE_HARDWARE的視圖肮之,底層RenderNode內(nèi)部創(chuàng)建一個Layer。

到這里眶明,就結(jié)束了根RenderNode的prepareTreeImpl方法筐高,并遞歸遍歷子節(jié)點的prepareTreeImpl。繼續(xù)回到CanvasContext的prepareTree柑土。
接下來是NativeWindow#query方法蜀肘。
那么何時初始化NativeWindow呢?

追溯到Java層稽屏,ViewRootImpl#performTraversals,調(diào)用ThreadedRenderer的initialize方法狐榔,該方法傳入由Wms初始化的Surface。

@Override
boolean initialize(Surface surface) throws OutOfResourcesException {
    mInitialized = true;
    updateEnabledState(surface);
    boolean status = nInitialize(mNativeProxy, surface);
    return status;
}

根據(jù)上層Surface獲取底層NativeWindow轿偎。從JNI#觸發(fā)RenderProxy#initialize方法被廓,到CanvasContext的initialize方法。

bool CanvasContext::initialize(ANativeWindow* window) {
    setSurface(window);
    mCanvas = new OpenGLRenderer(mRenderThread.renderState());
    mCanvas->initProperties();
    return true;
}

初始化OpenGLRenderer萝玷。
利用NativeWindow創(chuàng)建一個mEglSurface昆婿。

void CanvasContext::setSurface(ANativeWindow* window) {
    mNativeWindow = window;
    ...
    if (window) {
        mEglSurface = mEglManager.createSurface(window);
    }
}

NativeWindow是Java層Surface的底層對象蜓斧。
query方法的目的是查詢生產(chǎn)者和消費者緩沖區(qū)的運行情況。

NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND的官方解釋為是否消費者比生產(chǎn)者對緩沖區(qū)的處理落后大于1個緩沖區(qū)看疙,runningBehind返回結(jié)果直奋,如果runningBehind為false說明未落后能庆,則info.out.canDrawThisFrame置為true脚线,說明該幀可以繪制,否則canDrawThisFrame設為false跳過該幀渠旁。


總結(jié)

總結(jié):
主線程負責App視圖繪制記錄船逮,在App,每一次樹形視圖繪制都是一個渲染任務傻唾,渲染線程專注于與gpu交互。
第二步伪煤,prepareTree邏輯是繪制準備凛辣,將屬性與DisplayList數(shù)據(jù)交接,DrawRenderNodeOp操作對應節(jié)點遞歸處理,RenderNode必要時初始化Layer蝗敢,準備NativeWindow查詢是否該幀需要跳過寿谴。
syncFrameState同步結(jié)束,返回info.prepareTextures結(jié)果,賦值canUnblockUiThread码泞。
若info.prepareTextures標志余寥,喚醒主線程宋舷。Signal#signal()。
否則莲兢,說明存在未準備好的Texture续膳。先執(zhí)行CanvasContext#draw方法,再喚醒主線程坟岔。


任重而道遠

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末社付,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸥咖,更是在濱河造成了極大的恐慌,老刑警劉巖啊研,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸥拧,死亡現(xiàn)場離奇詭異,居然都是意外死亡沟娱,警方通過查閱死者的電腦和手機腕柜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門柳爽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碱屁,“玉大人蛾找,你說我怎么就攤上這事〈蛎” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵碰声,是天一觀的道長熬甫。 經(jīng)常有香客問我,道長瞻颂,這世上最難降的妖魔是什么郑象? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮盖矫,結(jié)果婚禮上击奶,老公的妹妹穿的比我還像新娘。我一直安慰自己柜砾,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布喜爷。 她就那樣靜靜地躺著萄唇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪另萤。 梳的紋絲不亂的頭發(fā)上诅挑,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天拔妥,我揣著相機與錄音没龙,去河邊找鬼硬纤。 笑死,一個胖子當著我的面吹牛溪王,可吹牛的內(nèi)容都是我干的值骇。 我是一名探鬼主播芒珠,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼娜汁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颅和,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蹭越,失蹤者是張志新(化名)和其女友劉穎响鹃,沒想到半個月后买置,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忿项,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了褐捻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡板壮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出透葛,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布靶草,位于F島的核電站,受9級特大地震影響寒随,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜试和,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一阅悍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寻行,春花似錦拌蜘、人聲如沸牙丽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骄噪。三九已至链蕊,卻和暖如春滔韵,著一層夾襖步出監(jiān)牢的瞬間掌实,已是汗流浹背宴卖。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工随闽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肝谭,地道東北人攘烛。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓坟漱,卻偏偏與公主長得像须眷,于是被迫代替她去往敵國和親沟突。 傳聞我的和親對象是個殘疾皇子惠拭,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容