顯示框架之VirtualDisplay 的數據源

Android 支持多個屏幕,主屏(主顯的物理屏),虛擬屏(非物理屏),外部顯示屏(折疊屏),其中主屏和外部顯示屏是實實在在的硬件物理屏,這兩者在SurfaceFlinger側的顯示流程相差不大,而VirtualDisplay雖然也是走的SurfaceFlinger流程,但數據源的方式有較大的不同,本文就分析下VirtualDisplay的數據源。
對VirtualDisplay框架層的分析可以看下這篇文章:http://www.lxweimin.com/p/c4ea60bc73d2
這里主要探索一下VirtualDisplay的數據源。

CreateDisplay

首先框架層會通過DMS來創建虛擬屏,通過jni調到SurfaceCompoerClient:: createDisplay,再通過binder調到SurfaceFlinger,看下SurfaceFlinger側:

文件:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure) {
 
    class DisplayToken : public BBinder {
        sp<SurfaceFlinger> flinger;
        virtual ~DisplayToken() {
             // no more references, this display must be terminated
             Mutex::Autolock _l(flinger->mStateLock);
             flinger->mCurrentState.displays.removeItem(this);
             flinger->setTransactionFlags(eDisplayTransactionNeeded);
         }
     public:
        explicit DisplayToken(const sp<SurfaceFlinger>& flinger)
            : flinger(flinger) {
        }
    };
    // new了一個token ,這個是可以跨進程傳遞的對象
    sp<BBinder> token = new DisplayToken(this);

    Mutex::Autolock _l(mStateLock);
    // Display ID is assigned when virtual display is allocated by HWC.
    DisplayDeviceState state;
    state.isSecure = secure;
    state.displayName = displayName;
    // 把display的狀態存放在mCurrentState
    mCurrentState.displays.add(token, state);
    mInterceptor->saveDisplayCreation(state);
    return token;
}

CreateDisplay 的作用是創建了一個token返回給框架層,框架層通過這個token就能識別到這個display,然后存放到mCurrentState.displays 里面。

setDisplaySurface

WMS會通過這個接口給SurfaceFlinger傳一個Surface,這個Surface是創建VirtualDisplay的進程用來顯示內容的。要注意這個Surface與SurfaceFlinger不在同一個進程。


setDisplaySurface的callback.png
文件:frameworks/native/libs/gui/SurfaceComposerClient.cpp

status_t SurfaceComposerClient::Transaction::setDisplaySurface(const sp<IBinder>& token,
        const sp<IGraphicBufferProducer>& bufferProducer) {
    ...
    DisplayState& s(getDisplayState(token));
    // 設置DisplayState的surface
    s.surface = bufferProducer;
    s.what |= DisplayState::eSurfaceChanged;
    return NO_ERROR;
}

這個接口的主要作用就是設置surface給SurfaceFlinger,這個surface是個BufferQufferProducer對象,由vds所在的進程創建而成。

processDisplayAdded

有display發生變化時,transactionFlags 就會被置上eDisplayTransactionNeeded 這個flag,有新增加的display時,就會走processDisplayAdded這個邏輯,這個函數承載著主要的邏輯。

文件:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

void SurfaceFlinger::processDisplayAdded(const wp<IBinder>& displayToken,
                                         const DisplayDeviceState& state) {
    ...
    // 物理屏的邏輯,虛擬屏不走
    if (state.physical) {
        const auto& activeConfig =
                getCompositionEngine().getHwComposer().getActiveConfig(state.physical->id);
        width = activeConfig->getWidth();
        height = activeConfig->getHeight();
        pixelFormat = static_cast<ui::PixelFormat>(PIXEL_FORMAT_RGBA_8888);
    } else if (state.surface != nullptr) {
        // 虛擬屏邏輯走這里,查詢傳進來的Surface的寬,高,格式
        int status = state.surface->query(NATIVE_WINDOW_WIDTH, &width);
        ALOGE_IF(status != NO_ERROR, "Unable to query width (%d)", status);
        status = state.surface->query(NATIVE_WINDOW_HEIGHT, &height);
        ALOGE_IF(status != NO_ERROR, "Unable to query height (%d)", status);
        int intPixelFormat;
        status = state.surface->query(NATIVE_WINDOW_FORMAT, &intPixelFormat);
        ALOGE_IF(status != NO_ERROR, "Unable to query format (%d)", status);
        pixelFormat = static_cast<ui::PixelFormat>(intPixelFormat);
        ...
    } else {
        // Virtual displays without a surface are dormant:
        // they have external state (layer stack, projection,
        // etc.) but no internal state (i.e. a DisplayDevice). 
        return;
    }

  compositionengine::DisplayCreationArgsBuilder builder;
    if (const auto& physical = state.physical) {
        // 如果是主屏則設置display id
        builder.setPhysical({physical->id, physical->type});
    }
    // 設置display的屬性參數
    builder.setPixels(ui::Size(width, height));
    builder.setPixelFormat(pixelFormat);
    builder.setIsSecure(state.isSecure);
    builder.setLayerStackId(state.layerStack);
    builder.setPowerAdvisor(&mPowerAdvisor);
    // 設置是否支持使用HWC合成 VDS
    builder.setUseHwcVirtualDisplays((mUseHwcVirtualDisplays && canAllocateHwcForVDS) ||
                                     getHwComposer().isUsingVrComposer());
    builder.setName(state.displayName);
    
     // 創建compositionDisplay,這個函數的作用是創建對應的Output和Display
    const auto compositionDisplay = getCompositionEngine().createDisplay(builder.build());

    sp<compositionengine::DisplaySurface> displaySurface;
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferProducer> bqProducer;
    sp<IGraphicBufferConsumer> bqConsumer;

    // 創建一個BufferQueue,拿到對應的BufferQueueProducer和BufferQueueConsumer
    getFactory().createBufferQueue(&bqProducer, &bqConsumer, /*consumerIsSurfaceFlinger =*/false);

    std::optional<DisplayId> displayId = compositionDisplay->getId();

    if (state.isVirtual()) {
        // 創建VirtualDisplaySurface
        sp<VirtualDisplaySurface> vds =
                new VirtualDisplaySurface(getHwComposer(), displayId, state.surface,
                                          bqProducer, bqConsumer, state.displayName,
                                          state.isSecure);

         // 將vds設置給displaySurface 和 producer 
        displaySurface = vds;
        producer = vds;
    } else {
       // 主屏會創建FrameBufferSurface
        ...
    }

   // 創建nativeWindowSurface和displaydevice
  const auto display = setupNewDisplayDeviceInternal(displayToken, compositionDisplay, state,
                                                       displaySurface, producer);
  mDisplays.emplace(displayToken, display);
  ...
}

(1) getCompositionEngine().createDisplay(builder.build()) 會創建相應的Output和 Display對象,對應的類序圖如下:


display的類序圖.png

(2)new VirtualDisplaySurface,創建VDS,將surface作為 mSource[SOURCE_SINK]

文件:frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp

VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc,
                                             const std::optional<DisplayId>& displayId,
                                             const sp<IGraphicBufferProducer>& sink,
                                             const sp<IGraphicBufferProducer>& bqProducer,
                                             const sp<IGraphicBufferConsumer>& bqConsumer,
                                             const std::string& name, bool secure)
      : ConsumerBase(bqConsumer),
       ... {
    // 將surface作為mSource[SOURCE_SINK], BufferQueueProducer作為mSource[SOURCE_SCRATCH]
    mSource[SOURCE_SINK] = sink;
    mSource[SOURCE_SCRATCH] = bqProducer;

    resetPerFrameState();
                                                
    int sinkWidth, sinkHeight;

    // 查詢surface的寬高
    sink->query(NATIVE_WINDOW_WIDTH, &sinkWidth);
    sink->query(NATIVE_WINDOW_HEIGHT, &sinkHeight);
    mSinkBufferWidth = sinkWidth;
    mSinkBufferHeight = sinkHeight;
                                                       
    //  查詢和設置usage,format
    int sinkUsage;
    sink->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &sinkUsage);
    mSinkUsage |= (GRALLOC_USAGE_HW_COMPOSER | sinkUsage);
    setOutputUsage(mSinkUsage);
    if (sinkUsage & (GRALLOC_USAGE_SW_READ_MASK | GRALLOC_USAGE_SW_WRITE_MASK)) {
        int sinkFormat;
        sink->query(NATIVE_WINDOW_FORMAT, &sinkFormat);
        mDefaultOutputFormat = sinkFormat;
    } else {
        mDefaultOutputFormat = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
    }
    mOutputFormat = mDefaultOutputFormat;

    // 設置BufferQueueConsumer的name,usage,BufferSize
    ConsumerBase::mName = String8::format("VDS: %s", mDisplayName.c_str());
    mConsumer->setConsumerName(ConsumerBase::mName);
    mConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
    mConsumer->setDefaultBufferSize(sinkWidth, sinkHeight);
    sink->setAsyncMode(true);
    IGraphicBufferProducer::QueueBufferOutput output;
    mSource[SOURCE_SCRATCH]->connect(nullptr, NATIVE_WINDOW_API_EGL, false, &output);
}

這一步最重要的就是將surface設置成了mSource[SOURCE_SINK]。
(3) setupNewDisplayDeviceInternal 這個函數創建了nativeWindowSurface和displaydevice

文件:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
        const wp<IBinder>& displayToken,
        std::shared_ptr<compositionengine::Display> compositionDisplay,
        const DisplayDeviceState& state,
        const sp<compositionengine::DisplaySurface>& displaySurface,
        const sp<IGraphicBufferProducer>& producer) {

     ...
    // 將producer作為參數創建一個NativeWindowSurface,這個producer就是vds
    auto nativeWindowSurface = getFactory().createNativeWindowSurface(producer);
    auto nativeWindow = nativeWindowSurface->getNativeWindow();
    creationArgs.nativeWindow = nativeWindow;

    // Make sure that composition can never be stalled by a virtual display
    // consumer that isn't processing buffers fast enough. We have to do this
    // here, in case the display is composed entirely by HWC.
    if (state.isVirtual()) {
        nativeWindow->setSwapInterval(nativeWindow.get(), 0);
    }
    ...

這里重要的是將vds作為producer創建了Surface


vds類序圖.png
文件:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
        const wp<IBinder>& displayToken,
        std::shared_ptr<compositionengine::Display> compositionDisplay,
        const DisplayDeviceState& state,
        const sp<compositionengine::DisplaySurface>& displaySurface,
        const sp<IGraphicBufferProducer>& producer) {

   ...
    // 虛擬屏一直設為power on
    creationArgs.initialPowerMode = state.isVirtual() ? hal::PowerMode::ON : hal::PowerMode::OFF;

    // 創建DisplayDevice
    sp<DisplayDevice> display = getFactory().createDisplayDevice(creationArgs);

   ...
    // 設置vds displaydevice的參數
    display->setLayerStack(state.layerStack);
    display->setProjection(state.orientation, state.viewport, state.frame);
    display->setDisplayName(state.displayName);
}

文件:frameworks/native/services/surfaceflinger/DisplayDevice.cpp

DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args)
      : mFlinger(args.flinger),
        mDisplayToken(args.displayToken),
        mSequenceId(args.sequenceId),
        mConnectionType(args.connectionType),
        mCompositionDisplay{args.compositionDisplay},
        mPhysicalOrientation(args.physicalOrientation),
        mIsPrimary(args.isPrimary) {

    mCompositionDisplay->editState().isSecure = args.isSecure;
    // 創建RenderSurface,將vds 和 windowSurface 作為參數傳進來
    mCompositionDisplay->createRenderSurface(
            compositionengine::RenderSurfaceCreationArgs{ANativeWindow_getWidth(
                                                                 args.nativeWindow.get()),
                                                         ANativeWindow_getHeight(
                                                                 args.nativeWindow.get()),
                                                         args.nativeWindow, args.displaySurface});

    ...
}

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp

RenderSurface::RenderSurface(const CompositionEngine& compositionEngine, Display& display,
                             const RenderSurfaceCreationArgs& args)
      : mCompositionEngine(compositionEngine),
        mDisplay(display),
        //  mNativeWindow 為 surface對象   mDisplaySurface 為vds對象
        mNativeWindow(args.nativeWindow),
        mDisplaySurface(args.displaySurface),
        mSize(args.displayWidth, args.displayHeight) {
    LOG_ALWAYS_FATAL_IF(!mNativeWindow);
}

這一步最重要的就是創建了nativeWindowSurface和displaydevice對象,到這里初始化的流程就走完了。

數據源

vds創建后,跟著SurfaceFlinger主線程進行刷新,SurfaceFlinger refresh的幾個接口在“顯示框架之SurfaceFlinger Refresh流程”里面有分析,這個流程對于VDS沒差,但有些函數調用有差別,來看下:
Refresh主要執行的幾個函數:

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp

void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
    ATRACE_CALL();
    ALOGV(__FUNCTION__);

    updateColorProfile(refreshArgs);
    updateAndWriteCompositionState(refreshArgs);
    setColorTransform(refreshArgs);
    beginFrame();
    prepareFrame();
    devOptRepaintFlash(refreshArgs);
    finishFrame(refreshArgs);
    postFramebuffer();
}

beginFrame

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp

void Output::beginFrame() {
    ...
    mRenderSurface->beginFrame(mustRecompose);

    if (mustRecompose) {
        outputState.lastCompositionHadVisibleLayers = !empty;
    }
}

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp

status_t RenderSurface::beginFrame(bool mustRecompose) {
    return mDisplaySurface->beginFrame(mustRecompose);
}

文件:frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp

status_t VirtualDisplaySurface::beginFrame(bool mustRecompose) {
    // 因為hwc暫不支持vds功能,故這里displayid為null,直接return
    if (!mDisplayId) {
        return NO_ERROR;
    }

    mMustRecompose = mustRecompose;
    //For WFD use cases we must always set the recompose flag in order
    //to support pause/resume functionality
    if (mOutputUsage & GRALLOC_USAGE_HW_VIDEO_ENCODER) {
        mMustRecompose = true;
    }
    VDS_LOGW_IF(mDbgState != DBG_STATE_IDLE,
            "Unexpected beginFrame() in %s state", dbgStateStr());
    mDbgState = DBG_STATE_BEGUN;

    return refreshOutputBuffer();
}

注意這里因為hwc不支持vds,故displayid 為null,直接return,沒做什么事情。

prepareFrame

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp

void Output::prepareFrame() {
    ATRACE_CALL();
    ALOGV(__FUNCTION__);

    const auto& outputState = getState();
    if (!outputState.isEnabled) {
        return;
    }
     // 沒有hwcid,故直接走GPU合成
    chooseCompositionStrategy();

    mRenderSurface->prepareFrame(outputState.usesClientComposition,
                                 outputState.usesDeviceComposition);
}

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp

void RenderSurface::prepareFrame(bool usesClientComposition, bool usesDeviceComposition) {
    DisplaySurface::CompositionType compositionType;
   ...
    } else if (usesClientComposition) {
        // 這里直接走GPU合成
        compositionType = DisplaySurface::COMPOSITION_GPU;
    ...
    if (status_t result = mDisplaySurface->prepareFrame(compositionType); result != NO_ERROR) {
        ALOGE("updateCompositionType failed for %s: %d (%s)", mDisplay.getName().c_str(), result,
              strerror(-result));
    }
}

文件:frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp

status_t VirtualDisplaySurface::prepareFrame(CompositionType compositionType) {
    // 沒有Displayid 直接return
    if (!mDisplayId) {
        return NO_ERROR;
    }

   ...
}

這個函數的作用就是判斷了vds的合成類型,因為hwc不支持的原因,所以目前走GPU合成。

finishFrame

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp

void Output::finishFrame(const compositionengine::CompositionRefreshArgs& refreshArgs) {
    ...
    // dequeueBuffer
    auto optReadyFence = composeSurfaces(Region::INVALID_REGION, refreshArgs);
    if (!optReadyFence) {
        return;
    }

    // swap buffers (presentation)
    mRenderSurface->queueBuffer(std::move(*optReadyFence));
}

std::optional<base::unique_fd> Output::composeSurfaces(
        const Region& debugRegion, const compositionengine::CompositionRefreshArgs& refreshArgs) {
    ...
    base::unique_fd fd;
    sp<GraphicBuffer> buf;

    // If we aren't doing client composition on this output, but do have a
    // flipClientTarget request for this frame on this output, we still need to
    // dequeue a buffer.
    if (hasClientComposition || outputState.flipClientTarget) {
        buf = mRenderSurface->dequeueBuffer(&fd);
        if (buf == nullptr) {
            ALOGW("Dequeuing buffer for display [%s] failed, bailing out of "
                  "client composition for this frame",
                  mName.c_str());
            return {};
        }
    }
   // GPU合成的邏輯
   ...
}

文件:frameworks/native/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp

sp<GraphicBuffer> RenderSurface::dequeueBuffer(base::unique_fd* bufferFence) {
    ATRACE_CALL();
    int fd = -1;
    ANativeWindowBuffer* buffer = nullptr;

    // 主要的區別在這里,mNativeWindow 為Surface對象,調到Surface.cpp里面的dequeueBuffer,然后再調到VirtualDisplay的deququBuffer
    status_t result = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buffer, &fd);

    if (result != NO_ERROR) {
        ALOGE("ANativeWindow::dequeueBuffer failed for display [%s] with error: %d",
              mDisplay.getName().c_str(), result);
        // Return fast here as we can't do much more - any rendering we do
        // now will just be wrong.
        return mGraphicBuffer;
    }

    ALOGW_IF(mGraphicBuffer != nullptr, "Clobbering a non-null pointer to a buffer [%p].",
             mGraphicBuffer->getNativeBuffer()->handle);
    mGraphicBuffer = GraphicBuffer::from(buffer);

    *bufferFence = base::unique_fd(fd);

    return mGraphicBuffer;
}

文件:frameworks/native/libs/gui/Surface.cpp 

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
...
  // 這里mGraphicBufferProducer 對象為VirtualDisplaySurface
 status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
                                                          reqFormat, reqUsage, &mBufferAge,
                                                          enableFrameTimestamps ? &frameTimestamps
                                                                                   : nullptr);
...
}

文件:frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp

status_t VirtualDisplaySurface::dequeueBuffer(int* pslot, sp<Fence>* fence, uint32_t w, uint32_t h,
                                              PixelFormat format, uint64_t usage,
                                              uint64_t* outBufferAge,
                                              FrameEventHistoryDelta* outTimestamps) {
    if (!mDisplayId) {
        // 這里執行的是mSource[SOURCE_SINK] 的dequeueBuffer,mSource[SOURCE_SINK] 實質上就是應用傳進來的surface
        return mSource[SOURCE_SINK]->dequeueBuffer(pslot, fence, w, h, format, usage, outBufferAge,
                                                   outTimestamps);
    }

這里最重要的就是理解dequeueBuffer的執行對象是誰,RenderSurface::dequeueBuffer -> NativeWindow:: dequeueBuffer -> Surface::dequeueBuffer ->VirtualDisplaySurface:: dequeueBuffer-> sf對端進程的Surface::dequeueBuffer
同理,queueBuffer的執行對象跟dequeueBuffer一樣,RenderSurface::queueBuffer -> NativeWindow:: queueBuffer -> Surface::queueBuffer ->VirtualDisplaySurface:: queueBuffer-> sf對端進程的Surface::queueBuffer
可以看出來dequeueBuffer和queueBuffer都是在sf對端進程實現,從systrace也可以看到,這里SurfaceFlinger作為Client端,media.codec為Server端。


SurfaceFlinger Client端.png

media.codec server端.png

在 ”顯示框架之SurfaceFlinger GPU合成 “ 分析到dequeueBuffer出來的Buffer作為輸出的Buffer,輸入為當前layer的Buffer,可以理解為GPU將輸入的n塊Buffer合成輸出到1塊Buffer,具體流程可以看“顯示框架之SurfaceFlinger GPU合成” 的分析,可以看到其實數據源就是GPU合成的這塊Buffer, 交給media.codec去消費,這塊Buffer的acquire進程也是media.codec。


media.codec acquireBuffer.png

之后Buffer就給到media去處理了


mediaserver處理buffer.png

總結:對于VDS,SurfaceFlinger是作為Client端,GPU合成的結果是數據源,所以虛擬屏顯示的內容和主屏是一樣的。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容