BufferQueue筆記

應(yīng)用程序的一個(gè)窗口通過ViewRootImpl的relayout來向SurfaceFlinger請(qǐng)求創(chuàng)建Surface時(shí),會(huì)在SurfaceFlinger這邊創(chuàng)建一個(gè)Layer對(duì)象。Layer對(duì)象創(chuàng)建時(shí)會(huì)創(chuàng)建一個(gè)Buffer隊(duì)列。因?yàn)槭莟riple buffer苛白,所以MaxDequeuedBufferCount被設(shè)置為2藐唠。

void Layer::onFirstRef() {
    // Creates a custom BufferQueue for SurfaceFlingerConsumer to use
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
    BufferQueue::createBufferQueue(&producer, &consumer);
    mProducer = new MonitoredProducer(producer, mFlinger);
    mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName);

#ifdef TARGET_DISABLE_TRIPLE_BUFFERING
#warning "disabling triple buffering"
#else
    mProducer->setMaxDequeuedBufferCount(2);
#endif

    const sp<const DisplayDevice> hw(mFlinger->getDefaultDisplayDevice());
    updateTransformHint(hw);
}
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
        sp<IGraphicBufferConsumer>* outConsumer,
        const sp<IGraphicBufferAlloc>& allocator) {
    ....
    sp<BufferQueueCore> core(new BufferQueueCore(allocator));
    LOG_ALWAYS_FATAL_IF(core == NULL,
            "BufferQueue: failed to create BufferQueueCore");

    sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core));
    LOG_ALWAYS_FATAL_IF(producer == NULL,
            "BufferQueue: failed to create BufferQueueProducer");

    sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
    LOG_ALWAYS_FATAL_IF(consumer == NULL,
            "BufferQueue: failed to create BufferQueueConsumer");

    *outProducer = producer;
    *outConsumer = consumer;
}

};

BufferQueueCore的構(gòu)造函數(shù)請(qǐng)求SurfaceFlinger創(chuàng)建一個(gè)GraphicBufferAlloc

BufferQueueCore::BufferQueueCore(const sp<IGraphicBufferAlloc>& allocator) :
    mAllocator(allocator),
......
    mMaxAcquiredBufferCount(1),
    mMaxDequeuedBufferCount(1),
.....
{
    if (allocator == NULL) {
        sp<ISurfaceComposer> composer(ComposerService::getComposerService());
        mAllocator = composer->createGraphicBufferAlloc();
        if (mAllocator == NULL) {
            BQ_LOGE("createGraphicBufferAlloc failed");
        }
    }

    int numStartingBuffers = getMaxBufferCountLocked(); //
    for (int s = 0; s < numStartingBuffers; s++) {
        mFreeSlots.insert(s);
    }
    for (int s = numStartingBuffers; s < BufferQueueDefs::NUM_BUFFER_SLOTS;
            s++) {
        mUnusedSlots.push_front(s);
    }
}

int BufferQueueCore::getMaxBufferCountLocked() const {
    int maxBufferCount = mMaxAcquiredBufferCount + mMaxDequeuedBufferCount +
            ((mAsyncMode || mDequeueBufferCannotBlock) ? 1 : 0);

    // limit maxBufferCount by mMaxBufferCount always
    maxBufferCount = std::min(mMaxBufferCount, maxBufferCount);

    return maxBufferCount;
}
sp<IGraphicBufferAlloc> SurfaceFlinger::createGraphicBufferAlloc()
{
    sp<GraphicBufferAlloc> gba(new GraphicBufferAlloc());
    return gba;
}

BufferQueueCore里面有個(gè)SlotsType的變量mSlots,其實(shí)是一個(gè)BufferSlot數(shù)組宦棺。一個(gè)BufferSlot包含了一塊buffer所對(duì)應(yīng)GraphicBuffer舌缤,還有buffer的狀態(tài)镀岛。

SlotsType BufferQueueCore.mSlots: 

enum { NUM_BUFFER_SLOTS = 64 };

typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];

struct BufferSlot {

    // mGraphicBuffer points to the buffer allocated for this slot or is NULL
    // if no buffer has been allocated.
    sp<GraphicBuffer> mGraphicBuffer;

    // mEglDisplay is the EGLDisplay used to create EGLSyncKHR objects.
    EGLDisplay mEglDisplay;

    // mBufferState is the current state of this buffer slot.
    BufferState mBufferState;

    // mRequestBufferCalled is used for validating that the producer did
    // call requestBuffer() when told to do so. Technically this is not
    // needed but useful for debugging and catching producer bugs.
    bool mRequestBufferCalled;

    // mFrameNumber is the number of the queued frame for this slot.  This
    // is used to dequeue buffers in LRU order (useful because buffers
    // may be released before their release fence is signaled).
    uint64_t mFrameNumber;

    // mEglFence is the EGL sync object that must signal before the buffer
    // associated with this buffer slot may be dequeued. It is initialized
    // to EGL_NO_SYNC_KHR when the buffer is created and may be set to a
    // new sync object in releaseBuffer.  (This is deprecated in favor of
    // mFence, below.)
    EGLSyncKHR mEglFence;

    // mFence is a fence which will signal when work initiated by the
    // previous owner of the buffer is finished. When the buffer is FREE,
    // the fence indicates when the consumer has finished reading
    // from the buffer, or when the producer has finished writing if it
    // called cancelBuffer after queueing some writes. When the buffer is
    // QUEUED, it indicates when the producer has finished filling the
    // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
    // passed to the consumer or producer along with ownership of the
    // buffer, and mFence is set to NO_FENCE.
    sp<Fence> mFence;

    // Indicates whether this buffer has been seen by a consumer yet
    bool mAcquireCalled;

    // Indicates whether the buffer was re-allocated without notifying the
    // producer. If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when
    // dequeued to prevent the producer from using a stale cached buffer.
    bool mNeedsReallocation;
};

struct BufferState {

    uint32_t mDequeueCount;
    uint32_t mQueueCount;
    uint32_t mAcquireCount;
    bool mShared;

    // A buffer can be in one of five states, represented as below:
    //
    //         | mShared | mDequeueCount | mQueueCount | mAcquireCount |
    // --------|---------|---------------|-------------|---------------|
    // FREE    |  false  |       0       |      0      |       0       |
    // DEQUEUED|  false  |       1       |      0      |       0       |
    // QUEUED  |  false  |       0       |      1      |       0       |
    // ACQUIRED|  false  |       0       |      0      |       1       |
    // SHARED  |  true   |      any      |     any     |      any      |
}

一塊Buffer狀態(tài)轉(zhuǎn)移:繪制圖像時(shí),Surface會(huì)從BufferQueue中dequeue中一塊buffer出來進(jìn)行繪制友驮,最多可以dequeue兩塊,繪制完成后queue到bufferQueue中驾锰,然后BufferConsumer(SurfaceFlinger)可以從BufferQueue中acquire一塊buffer進(jìn)行多個(gè)窗口layer的合成渲染卸留,然后顯示到屏幕上。

buffer_state.png

以硬件渲染UI為例椭豫,分析一下BufferQueue的使用過程

ViewRootImpl.performTraversal會(huì)用Surface初始化ThreadedRenderer

hwInitialized = mAttachInfo.mHardwareRenderer.initialize(mSurface);

initialize方法內(nèi)部調(diào)用native方法耻瑟。

static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jobject jsurface) {
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
    proxy->initialize(surface);
}

最后調(diào)用CanvasContext.initialize

void CanvasContext::initialize(Surface* surface) {
    setSurface(surface);
}

void CanvasContext::setSurface(Surface* surface) {
    mNativeSurface = surface;

    if (surface) {
        mEglSurface = mEglManager.createSurface(surface);
    }

EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr);

    return surface;
}

/frameworks/native/opengl/libs/EGL/eglApi.cpp

EGLSurface eglCreateWindowSurface(  EGLDisplay dpy, EGLConfig config,
                                    NativeWindowType window,
                                    const EGLint *attrib_list)
{
    int result = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
    
    EGLSurface surface = cnx->egl.eglCreateWindowSurface(
            iDpy, config, window, attrib_list);
    if (surface != EGL_NO_SURFACE) {
        egl_surface_t* s = new egl_surface_t(dp.get(), config, window,
                surface, cnx);
        return s;
    }
    return EGL_NO_SURFACE;
}

其中native_window_api_connect會(huì)調(diào)用到Surface::connect,api=NATIVE_WINDOW_API_EGL

int Surface::connect(int api, const sp<IProducerListener>& listener) {

    int err = mGraphicBufferProducer->connect(listener, api, mProducerControlledByApp, &output);

    return err;
}

BufferQueueProducer::connect里設(shè)置了BufferQueueCore里的一些參數(shù)赏酥。

status_t BufferQueueProducer::connect(const sp<IProducerListener>& listener,
        int api, bool producerControlledByApp, QueueBufferOutput *output) {

    int delta = mCore->getMaxBufferCountLocked(mCore->mAsyncMode,
            mDequeueTimeout < 0 ?
            mCore->mConsumerControlledByApp && producerControlledByApp : false,
            mCore->mMaxBufferCount) -
            mCore->getMaxBufferCountLocked();
    if (!mCore->adjustAvailableSlotsLocked(delta)) {
        BQ_LOGE("connect: BufferQueue failed to adjust the number of available "
                "slots. Delta = %d", delta);
        return BAD_VALUE;
    }

    mCore->mBufferHasBeenQueued = false;
    mCore->mDequeueBufferCannotBlock = false;
    if (mDequeueTimeout < 0) {
        mCore->mDequeueBufferCannotBlock =
                mCore->mConsumerControlledByApp && producerControlledByApp;
    }

    mCore->mAllowAllocation = true;

}

/frameworks/native/opengl/libagl/egl.cpp

EGLSurface eglCreateWindowSurface(  EGLDisplay dpy, EGLConfig config,
                                    NativeWindowType window,
                                    const EGLint *attrib_list)
{
    return createWindowSurface(dpy, config, window, attrib_list);
}

EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
    initialize();
    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr);
    .....
    return surface;
}

static EGLSurface createWindowSurface(EGLDisplay dpy, EGLConfig config,
        NativeWindowType window, const EGLint* /*attrib_list*/)
{

    egl_surface_t* surface;
    surface = new egl_window_surface_v2_t(dpy, config, depthFormat,
            static_cast<ANativeWindow*>(window));

    return surface;
}

到此為止創(chuàng)建了一個(gè)EglSurface保存在CanvasContext中

當(dāng)使用ThreadedRenderer去draw一幀displaylist時(shí)喳整,會(huì)調(diào)用nSyncAndDrawFrame

void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
    updateRootDisplayList(view, callbacks);
   
    int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
}

最終會(huì)調(diào)用到CanvasContext的draw方法:

void DrawFrameTask::run() {
    if (CC_LIKELY(canDrawThisFrame)) {
        context->draw();
    }
}

void CanvasContext::draw() {

    Frame frame = mEglManager.beginFrame(mEglSurface);
    ....
    if (drew || mEglManager.damageRequiresSwap()) {
        if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
            setSurface(nullptr);
        }
    }
}

CanvasContext的draw方法會(huì)先調(diào)用mEglManager.beginFrame,然后調(diào)用mEglManager.swapBuffers

Frame EglManager::beginFrame(EGLSurface surface) {
    .....
    makeCurrent(surface);
    ....
}
bool EglManager::makeCurrent(EGLSurface surface, EGLint* errOut) {

    if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
        ....
    }
    .....
}

EGLBoolean eglMakeCurrent(  EGLDisplay dpy, EGLSurface draw,
                            EGLSurface read, EGLContext ctx)
{

    if (ctx == EGL_NO_CONTEXT) {
        // if we're detaching, we need the current context
        current_ctx = (EGLContext)getGlThreadSpecific();
    } else {
       
        egl_surface_t* d = (egl_surface_t*)draw;
    }

    if (d) {
        if (d->connect() == EGL_FALSE) {
            return EGL_FALSE;
        }
    }
    return setError(EGL_BAD_ACCESS, EGL_FALSE);
}

EGLBoolean egl_window_surface_v2_t::connect()
{

    // dequeue a buffer
    int fenceFd = -1;
    if (nativeWindow->dequeueBuffer(nativeWindow, &buffer,
            &fenceFd) != NO_ERROR) {
        return setError(EGL_BAD_ALLOC, EGL_FALSE);
    }

    // wait for the buffer
    sp<Fence> fence(new Fence(fenceFd));
    if (fence->wait(Fence::TIMEOUT_NEVER) != NO_ERROR) {
        nativeWindow->cancelBuffer(nativeWindow, buffer, fenceFd);
        return setError(EGL_BAD_ALLOC, EGL_FALSE);
    }

    return EGL_TRUE;
}

beginFrame其實(shí)就是調(diào)用ANativeWindow的dequeueBuffer來獲取一個(gè)buffer裸扶。最終調(diào)用到Surface的dequeueBuffer

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ....
    int buf = -1;
    sp<Fence> fence;
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence,
            reqWidth, reqHeight, reqFormat, reqUsage);

    if (result < 0) {
        ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer"
                "(%d, %d, %d, %d) failed: %d", reqWidth, reqHeight, reqFormat,
                reqUsage, result);
        return result;
    }

    Mutex::Autolock lock(mMutex);

    sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);

    // this should never happen
    ALOGE_IF(fence == NULL, "Surface::dequeueBuffer: received null Fence! buf=%d", buf);

    if (result & IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
        freeAllBuffers();
    }

    if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) {
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
        if (result != NO_ERROR) {
            ALOGE("dequeueBuffer: IGraphicBufferProducer::requestBuffer failed: %d", result);
            mGraphicBufferProducer->cancelBuffer(buf, fence);
            return result;
        }
    }

    if (fence->isValid()) {
        *fenceFd = fence->dup();
        if (*fenceFd == -1) {
            ALOGE("dequeueBuffer: error duping fence: %d", errno);
            // dup() should never fail; something is badly wrong. Soldier on
            // and hope for the best; the worst that should happen is some
            // visible corruption that lasts until the next frame.
        }
    } else {
        *fenceFd = -1;
    }

    *buffer = gbuf.get();

    return OK;
}

GraphicBufferProducer::dequeueBuffer

status_t BufferQueueProducer::dequeueBuffer(int *outSlot,
        sp<android::Fence> *outFence, uint32_t width, uint32_t height,
        PixelFormat format, uint32_t usage) {

    { // Autolock scope
        Mutex::Autolock lock(mCore->mMutex);
        mCore->waitWhileAllocatingLocked();

        if (format == 0) {
            format = mCore->mDefaultBufferFormat;
        }

        // Enable the usage bits the consumer requested
        usage |= mCore->mConsumerUsageBits;

        const bool useDefaultSize = !width && !height;
        if (useDefaultSize) {
            width = mCore->mDefaultWidth;
            height = mCore->mDefaultHeight;
        }

        int found = BufferItem::INVALID_BUFFER_SLOT;
        while (found == BufferItem::INVALID_BUFFER_SLOT) {
            status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Dequeue,
                    &found);
            if (status != NO_ERROR) {
                return status;
            }
        }

        if (mCore->mSharedBufferSlot != found) {
            mCore->mActiveBuffers.insert(found);
        }
        *outSlot = found;
        ATRACE_BUFFER_INDEX(found);

        attachedByConsumer = mSlots[found].mNeedsReallocation;
        mSlots[found].mNeedsReallocation = false;

        mSlots[found].mBufferState.dequeue();

        if ((buffer == NULL) ||
                buffer->needsReallocation(width, height, format, usage))
        {
            mSlots[found].mAcquireCalled = false;
            mSlots[found].mGraphicBuffer = NULL;
            mSlots[found].mRequestBufferCalled = false;
            mSlots[found].mEglDisplay = EGL_NO_DISPLAY;
            mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
            mSlots[found].mFence = Fence::NO_FENCE;
            mCore->mBufferAge = 0;
            mCore->mIsAllocating = true;

            returnFlags |= BUFFER_NEEDS_REALLOCATION;
        } 
    } // Autolock scope

    if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
        status_t error;
        BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
        sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(
                width, height, format, usage, &error));
        { // Autolock scope
            Mutex::Autolock lock(mCore->mMutex);

            if (graphicBuffer != NULL && !mCore->mIsAbandoned) {
                graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
                mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
            }

            mCore->mIsAllocating = false;
            mCore->mIsAllocatingCondition.broadcast();
        }
    }


    return returnFlags;
}

GraphicBufferProducer::dequeueBuffer中會(huì)從BufferQueueCore中分配一個(gè)free slot框都,然后這個(gè)slot對(duì)應(yīng)的mSlots[found]的mGraphicBuffer是空的,這時(shí)會(huì)走到

 sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(width, height, format, usage, &error));

會(huì)調(diào)用GraphicBufferAlloc.createGraphicBuffer去從SurfaceFlinger分配一塊GraphicBuffer呵晨。GraphicBufferAlloc繼承自BnGraphicBufferAlloc

class GraphicBufferAlloc : public BnGraphicBufferAlloc
sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t width,
        uint32_t height, PixelFormat format, uint32_t usage, status_t* error) {
    sp<GraphicBuffer> graphicBuffer(
            new GraphicBuffer(width, height, format, usage));
    status_t err = graphicBuffer->initCheck();
    *error = err;
    if (err != 0 || graphicBuffer->handle == 0) {
        if (err == NO_MEMORY) {
            GraphicBuffer::dumpAllocationsToSystemLog();
        }
        ALOGE("GraphicBufferAlloc::createGraphicBuffer(w=%d, h=%d) "
             "failed (%s), handle=%p",
                width, height, strerror(-err), graphicBuffer->handle);
        return 0;
    }
    return graphicBuffer;
}

GraphicBuffer實(shí)現(xiàn)了Flattenable接口來進(jìn)行序列化魏保,同時(shí)實(shí)現(xiàn)了ANativeWindowBuffer接口來供ANativeWindow來進(jìn)行繪制圖像:

class GraphicBuffer
    : public ANativeObjectBase< ANativeWindowBuffer, GraphicBuffer, RefBase >,
      public Flattenable<GraphicBuffer>

GraphicBuffer的構(gòu)造函數(shù)里請(qǐng)求gralloc來分配緩存熬尺。這塊緩存應(yīng)該是SurfaceFlinger和App共享的。

GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage)
    : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
      mInitCheck(NO_ERROR), mId(getUniqueId()), mGenerationNumber(0)
{
    width  =
    height =
    stride =
    format =
    usage  = 0;
    handle = NULL;
    mInitCheck = initSize(inWidth, inHeight, inFormat, inUsage);
}

status_t GraphicBuffer::initSize(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage)
{
    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
    status_t err = allocator.alloc(inWidth, inHeight, inFormat, inUsage,
            &handle, &outStride); //通過GraphicBufferAllocator來分配緩存
    return err;
}

status_t GraphicBufferAllocator::alloc(uint32_t width, uint32_t height,
        PixelFormat format, uint32_t usage, buffer_handle_t* handle,
        uint32_t* stride)
{

    err = mAllocDev->alloc(mAllocDev, static_cast<int>(width),
            static_cast<int>(height), format, static_cast<int>(usage), handle,
            &outStride);
    *stride = static_cast<uint32_t>(outStride);

    return err;
}

GraphicBufferAllocator::GraphicBufferAllocator()
    : mAllocDev(0)
{
    hw_module_t const* module;
    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    ALOGE_IF(err, "FATAL: can't find the %s module", GRALLOC_HARDWARE_MODULE_ID);
    if (err == 0) {
        gralloc_open(module, &mAllocDev);
    }
}

mAllocDev應(yīng)該是通過gralloc_open賦值的谓罗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粱哼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子檩咱,更是在濱河造成了極大的恐慌揭措,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刻蚯,死亡現(xiàn)場離奇詭異绊含,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)芦倒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門艺挪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兵扬,你說我怎么就攤上這事麻裳。” “怎么了器钟?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵津坑,是天一觀的道長。 經(jīng)常有香客問我傲霸,道長疆瑰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任昙啄,我火速辦了婚禮穆役,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梳凛。我一直安慰自己耿币,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布韧拒。 她就那樣靜靜地躺著淹接,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叛溢。 梳的紋絲不亂的頭發(fā)上塑悼,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音楷掉,去河邊找鬼厢蒜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的郭怪。 我是一名探鬼主播支示,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼鄙才!你這毒婦竟也來了颂鸿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤攒庵,失蹤者是張志新(化名)和其女友劉穎嘴纺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浓冒,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栽渴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稳懒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闲擦。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖场梆,靈堂內(nèi)的尸體忽然破棺而出墅冷,到底是詐尸還是另有隱情,我是刑警寧澤或油,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布寞忿,位于F島的核電站,受9級(jí)特大地震影響顶岸,放射性物質(zhì)發(fā)生泄漏腔彰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一辖佣、第九天 我趴在偏房一處隱蔽的房頂上張望霹抛。 院中可真熱鬧,春花似錦卷谈、人聲如沸上炎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至寇损,卻和暖如春凸郑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矛市。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工芙沥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓而昨,卻偏偏與公主長得像救氯,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歌憨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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