BufferQueue学习

上一篇说完App申请完vsync后要进行绘制,本来要说一下绘制的过程,但是感觉进度有点慢,主要研究sf,所以转换下学习思路。

一、BufferQueue基本概念:

BufferQueue的核心逻辑是生产者消费者逻辑又是GraphicBuffer管理者,在BufferQueue这个生产者消费者框架中, BufferQueuecore可以理解为数据缓冲区的管理者,代码逻辑在BufferQueue.cpp和BufferQueuecore.cpp中。它的消费者是BufferQueueConsumer,生产者是BufferQueueProducer。
先看一张比较权威的BufferQueue相关基本操作流程,

image

首先初始状态下,所有可用BufferSlot全是FREE状态,在mFreeSlots中管理。

  • Producer 从 BufferQueue中dequeue一个FREE BufferSlot, Dequeue成功后,BufferSlot状态从FREE变为DEQUEUED;
  • Producer端填充好BufferSlot数据后,queue到BufferQueue中,BufferSlot状态从DEQUEUED变为QUEUED,且调用消费者onFrameAvailable通知它有可消费的BufferSlot;
  • Consumer从BufferQueue中acquire一个有数据的BufferSlot,BufferSlot状态从QUEUED变为ACQUIRED;
  • Consumer 使用完BufferSlot后,BufferSlot状态从ACQUIRED变为FREE
    总结一下BufferSlot的5种状态:FREE、DEQUEUED、QUEUED、ACQUIRED、SHARED。

从源码中看下buffer状态改变原理

/frameworks/native/include/gui/BufferSlot.h
 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      |

举例一个mQueueCount变化

    inline bool isQueued const {
        return  mQueueCount> 0;
    }

    inline void queue() {
       if (mDequeueCount > 0) {
           mDequeueCount--;
       }
       mQueueCount++;
   }

queue()方法调用后,mQueueCount++,isQueued() 方法返回值当然就为true;
可以明显的看到 buffer的状态由引用计数来表示。

二、BufferQueue内部结构

/frameworks/native/libs/gui/BufferQueue.cpp
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
        sp<IGraphicBufferConsumer>* outConsumer,
        bool consumerIsSurfaceFlinger) {

    sp<BufferQueueCore> core(new BufferQueueCore());
    sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core, consumerIsSurfaceFlinger));
    sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
    *outProducer = producer;
    *outConsumer = consumer;
}

通过其createBufferQueue方法可以看到会创建
BufferQueueCore、BufferQueueProducer、BufferQueueConsumer。
BufferQueueCore 负责维护 BufferQueue 的基本数据结构,而 BufferQueueProducer 和 BufferQueueConsumer 则负责提供操作 BufferQueue 的基本接口。

三、BufferSlot和BufferItem详解

1、BufferSlot和GraphicBuffer关联

BufferQueueCore中管理着数据缓冲区,而数据的核心GraphicBuffer关联在BufferSlot中。

/frameworks/native/include/gui/BufferQueueCore.h
class BufferQueueCore : public virtual RefBase {
......
    BufferQueueDefs::SlotsType mSlots;
}  @1

/frameworks/native/libs/gui/include/gui/BufferQueueDefs.h
namespace BufferQueueDefs {
   Typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
} @2

/frameworks/native/libs/gui/include/gui/BufferSlot.h
struct BufferSlot {
     sp<GraphicBuffer> mGraphicBuffer;
}  @3

由@1知BufferQueueCore定义了BufferQueueDefs::SlotsType mSlots,
由@2知mSlots实际上是一个BufferSolt的数组。
由@3知 BufferSolt中定义了一个GraphicBuffer的強指针用于关联mGraphicBuffer。
这样BufferSlot 和 GraphicBuffer 就关联上了。

2、BuffetItem关联BufferSlot

BufferQueue框架中,消费者和生产者对缓冲区数据操作的单元核心就是一个BufferSlot,也就是取GraphicBuffer,放GraphicBuffer的操作其实是针对BufferSlot来完成的。

/frameworks/native/libs/gui/include/gui/BufferQueueCore.h
class BufferQueueCore : public virtual RefBase {
    ......
typedef Vector<BufferItem> Fifo;
......
  Fifo mQueue;
}

/frameworks/native/libs/gui/include/gui/BufferItem.h
class BufferItem:public Flattenable<BufferItem>{
    //mSlot is the slot idex of this buffer(default INVALID_BUFFER_SLOT)
    Int mSlot;
}
  • BufferSlot 的存和取是在BufferQueueCore中定义了Fifo mQueue,Fifo是向量集,里面存的是BufferItem,而 BufferItem中又定义了mSlot的索引值。这样就可以和BufferSlot关联上了,可以在dequeue,queue,acquire方法中看出其关系。
  • 生产者从mQueue上获取BufferItem从而找到了对应的BufferSlot,并对它完成一系列的操作之后,放回到mQueue中供消费者使用,消费者也是从mQueue上获取BufferItem从而找到对应的BufferSlot来消费,消费完成之后放回mQueue。不过需要注意实际上不是真正的把BufferSlot取出放回mQueue,而是mSlots索引值的传递过程。

3、BufferSlot状态说明

image.png

说明:
mSlots BufferSlot结构体数组,数组长度为64

  • mFreeSlots BufferSlot状态为FREE,且没有GraphicBuffer与之相绑定的slot集合
  • mFreeBuffers BufferSlot状态为FREE,且有GraphicBuffer与之相绑定的slot集合
  • mActiveBuffers BufferSlot状态不为FREE(即DEQUEUED、QUEUED、ACQUIRED、SHARED)的slot集合。既然状态不是FREE,那么该BufferSlot必然有一个GraphicBuffer与之相绑定
  • mUnusedSlots 未参与使用的slot集合,由 mMaxBufferCount 决定
  • mSlots = mFreeSlots + mFreeBuffers + mActiveBuffers + mUnusedSlots

四、BufferQueue生产者和消费者详解

我们知道 一个widow对应一个layer,一个layer对应一个生产者消费者模型。
BufferQueueConsumer 和 BufferQueueProducer 分别对应文件BufferQueueConsumer.cpp 和 BufferQueueProducer.cpp 和 BufferQueueCore.cpp 在同级目录都在/frameworks/native/libs/gui目录下。

1、两个类的继承关系和重要函数:

BuffeQueueProducer继承关系:

(BufferQueueProducer.h) BufferQueueProducer : public BnGraphicBufferProducer,private IBinder::DeathRecipient

(IGraphicBufferProducer.h)BnGraphicBufferProducer : public BnInterface<IGraphicBufferProducer>

(IInterface.h)class BnInterface : public INTERFACE, public BBinder

可以看到BufferQueueProducer继承了BnGraphicBufferProducer而BnGraphicBufferProducer又继承了IGraphicBufferProducer和BnInterface ,来完成主要的BufferSlot的流转操作,也提供了远程代理接口,实现跨进程binder调用。
同时BufferQueueProducer还继承了DeathRecipient 用来处理Binder死亡通知。

BuffeQueueProducer关键方法:

  • requestBuffer 获取对应BufferSlot的GraphicBuffer地址。
  • setMaxDequeuedBufferCount 设置最大同时可以dequeue出来的的BufferSlot数量。
  • dequeueBuffer 从FREE状态下的BufferSlots中队列中获取空闲的BufferSlot做生产使用,优先从mFreeBuffers中获取,如果没有则从mFreeSlots中获取。
  • attachBuffer 绑定已经分配好的GraphicBuffer到FREE状态下的BufferSlot中,优先从- mFreeSlots中查找BufferSlot,如果没有则从mFreeBuffers中查找并绑定。
  • queueBuffer 生产者把生产好的BufferSlot放到队列中供消费者使用。
  • detachBuffer 把attachBuffer了GraphicBuffer的 Active状态下的BufferSlot的放到mFreeBuffers之后直接取消GraphicBuffer的绑定。
  • detachNextBuffer 把需要释放的BufferSlot中的GraphicBuffer指针赋值到outBuffer输出之后,把BufferSlot的放到mFreeBuffers并解绑定GraphicBuffer。
  • cancelBuffer 把BufferSlot放回到mfreeBuffers中,不会释放graphicbuffer。
  • connect 生产者通过该接口把IProducerListener注册到BufferQueueCore中供消费者回调,同时建立了Binder死亡通知通路。
  • disconnect断开BufferQueueProducer和BufferQueueCore之间建立的链接关系。

BufferQueueConsumer继承关系:

(BufferQueueConsumer.h) BufferQueueConsumer : public BnGraphicBufferConsumer 

(IGraphicBufferConsumer.h)BnGraphicBufferConsumer : public SafeBnInterface<IGraphicBufferConsumer>

(SafeInterface.h)SafeBnInterface:public BnInterface<Interface>

可以看到BufferQueueConsumer的实现结构和BufferQueueProducer很相似继承了BnGraphicBufferConsumer 而BnGraphicBufferConsumer又最终继承了IGraphicBufferConsumer和SafeBnInterface,来完成主要的BufferSlot的流转操作,也提供了远程代理接口,实现跨进程binder调用。

BufferQueueConsumer关键方法:

  • acquireBuffer 获取QUEUE状态下的BufferSlot进行消费。
  • releaseBuffer消费完成之后把BufferSlot放回mFreeBuffers队列中。
  • attachBuffer 把消费者的GraphicBuffer绑定到BufferSlot上使用。
  • detachBuffer把消费者的GraphicBuffer从BufferSlot上解绑。
  • setMaxBufferCount 设置最大可用BufferSlot数量。
  • setMaxAcquiredBufferCount 设置最大同时可以acquire的BufferSlot数量。
  • connect 建立消费者和BufferQueueCore之间的链接,注册IConsumerListener回调。
  • disconnect 销毁消费者和BufferQueue之间的链接。

2、BufferQueueConsumer关键流程分析

(1). acquirebuffer的流程

主要流程是首先判断acquireBuffers是否已经超过最大一次能够Acquire的数量,mQueue是否为空,然后从mQueue中获取迭代器的第一个元素赋值给outBuffer输出,同时把mBufferState状态改为Acquired 并从mQueue中移除

status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
        nsecs_t expectedPresent, uint64_t maxFrameNumber) {
    ...
    {
        //判断AcquireBuffers是否已经超过最大一次能够Acquire的数量。
        if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
            return INVALID_OPERATION;
        }
        //判断mQueue队列是否为空
        if (mCore->mQueue.empty() && !sharedBufferAvailable) {
            return NO_BUFFER_AVAILABLE;
        }
        //mCore->mQueue.begin()返回iterator,指向第一个元素赋值给front
        BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
        if (sharedBufferAvailable && mCore->mQueue.empty()) {//共享buffer的处理逻辑
          ...
        } else {//正常非共享模式下的逻辑
            slot = front->mSlot;    //从front获取对应的slot,front是一个BufferItem指针
            *outBuffer = *front;    //把front指向的BufferItem赋值给outBuffer,outBuffer就是要的buffer
        }
 
        if (!outBuffer->mIsStale) {
            mSlots[slot].mAcquireCalled = true;
            if (mCore->mQueue.empty()) {
                mSlots[slot].mBufferState.acquireNotInQueue();
            } else {
                mSlots[slot].mBufferState.acquire();     //把BufferState修改成acquired状态
            }
        }
 
        mCore->mQueue.erase(front);       //把acquired slot 对应的BufferItem从mQueue中移除。
        mCore->mDequeueCondition.notify_all();
    }
    return NO_ERROR;
}

(2). releasebuffer的流程

releasebuffer主要流程是先做slot frameNumber 以及 BufferState有效性检查,修改mBufferState状态成FREE状态。然后把对应的slot从mActiveBuffers中移除并放回mFreeBuffers的过程。
这个过程中不做GraphicBuffer和BufferSlot的解绑定操作,也就是说GraphicBuffer不会被释放。

status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
        const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
        EGLSyncKHR eglFence) {
    //slot的合法性判断。
    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
            releaseFence == nullptr) {
        BQ_LOGE("releaseBuffer: slot %d out of range or fence %p NULL", slot,
                releaseFence.get());
        return BAD_VALUE;
    }
    sp<IProducerListener> listener;
    {
        //判断frameNumber是否有效,如果做过reallocated frameNumber将会改变。
        //这个frameNumber是什么?
        if (frameNumber != mSlots[slot].mFrameNumber &&
                !mSlots[slot].mBufferState.isShared()) {
            return STALE_BUFFER_SLOT;
        }
        //判断BufferState是否是acquired状态
        if (!mSlots[slot].mBufferState.isAcquired()) {
            return BAD_VALUE;
        }
        mSlots[slot].mBufferState.release();   //调用mBufferState.release()将acqiure状态释放。
        if (!mSlots[slot].mBufferState.isShared()) {
            mCore->mActiveBuffers.erase(slot);   //将slot从mActiveBuffers中移除
            mCore->mFreeBuffers.push_back(slot); //将slot放入到mFreeBuffers队列中
        }
        listener = mCore->mConnectedProducerListener;  //获取ProducerListener
        mCore->mDequeueCondition.notify_all();
    }
    if (listener != nullptr) {
        listener->onBufferReleased();  //调用Producer的onBufferReleased回调通知Producer完成释放。
    }
 
    return NO_ERROR;
}

3、BufferQueueProducer关键流程分析

(1). dequeuebuffer的流程

dequeuebuffer是生产者端从BufferQueueCore上获取一个GraphicBuffer进行生产的过程,生产者BufferQueueProducer 会在去获取一个FREE状态的的BufferSlot。
同时把mBufferState状态修改成Dequeue状态,把BufferSlot放到mActiveBuffers中管理。也由此可见GraphicBuffer是在这里实际创建的。

status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
                                            uint32_t width, uint32_t height, PixelFormat format,
                                            uint64_t usage, uint64_t* outBufferAge,
                                            FrameEventHistoryDelta* outTimestamps) {
    
        int found = BufferItem::INVALID_BUFFER_SLOT;
        //调用waitForFreeSlotThenRelock 找到可以dequeue的 FREE状态下的BufferSlot
        while (found == BufferItem::INVALID_BUFFER_SLOT) {
            status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Dequeue, lock, &found);
        }
        const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer); //把获取到的mGraphicBuffer地址赋值给buffer。
        if (mCore->mSharedBufferSlot != found) {
            mCore->mActiveBuffers.insert(found);   //把找到的slot放到mActiveBuffers中管理
        }
        *outSlot = found;  //赋值给outSlot输出
        mSlots[found].mBufferState.dequeue();  //修改BufferState 状态成dequeue状态。
    }
 
    return returnFlags;
}

FREE状态的BufferSlot又包含了mFreeSlots和mFreebuffers两组slots,dequeue的时候会先从mFreebuffers查找如果有可用的就使用,如果没有就从mFreeSlots获取BufferSlot并分配GraphicBuffer。
这个过程在waitForFreeSlotThenRelock中实现
下面是waitForFreeSlotThenRelock的流程:

status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
        std::unique_lock<std::mutex>& lock, int* found) const {
    auto callerString = (caller == FreeSlotCaller::Dequeue) ?
            "dequeueBuffer" : "attachBuffer";
    bool tryAgain = true;
    while (tryAgain) {
 
            if (){
            } else {
                if (caller == FreeSlotCaller::Dequeue) {   //Dequeuebuffer调用这段代码,先调用getFreeBufferLocked从mFreeBuffers中获取,如果找到了就返回。
                    // If we're calling this from dequeue, prefer free buffers
                    int slot = getFreeBufferLocked();
                    if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
                        *found = slot;
                    } else if (mCore->mAllowAllocation) {      // 如果没找到,在调用getFreeSlotLocked从mFreeSlots中获取。
                        *found = getFreeSlotLocked();
                    }
                } else {
                    // If we're calling this from attach, prefer free slots
                    int slot = getFreeSlotLocked();         //attachbuffer调用这段代码,优先先调用getFreeSlotLocked从mFreeSlots中获取,如果找到了就返回。
                    if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
                        *found = slot;
                    } else {
                        *found = getFreeBufferLocked();    如果没找到,在调用getFreeBufferLocked从mFreeBuffers中获取。
                    }
                }
            }
        }
    }
 
    return NO_ERROR;
}

int BufferQueueProducer::getFreeSlotLocked() const {
    if (mCore->mFreeSlots.empty()) {
        return BufferQueueCore::INVALID_BUFFER_SLOT;
    }
    int slot = *(mCore->mFreeSlots.begin());
    mCore->mFreeSlots.erase(slot);
    return slot;
}

(2). attachbuffer的流程

如上面的waitForFreeSlotThenRelock流程,attachBuffer也是从FREE状态的slots上获取BufferSlot,但是和dequeueBuffer不同attachBuffer是优先从mfreeslots上获取,如果mfreeslots没有,再从mfreebuffers上获取。
waitForFreeSlotThenRelock获取到BufferSlot之后,再把已有的申请好的GraphicBuffer绑定到这个BufferSlot上。同时把mBufferState状态修改成Dequeued状态。把BufferSlot放到mActiveBuffers中管理。

status_t BufferQueueProducer::attachBuffer(int* outSlot,
        const sp<android::GraphicBuffer>& buffer) {
 
    //调用waitForFreeSlotThenRelock 找到可以FREE状态下的BufferSlot
    status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Attach, lock, &found);
  
    *outSlot = found; //把找到的slot赋值给outSlot
    mSlots[*outSlot].mGraphicBuffer = buffer;  //把准备好的buffer关联到slot的mGraphicBuffer上
    mSlots[*outSlot].mBufferState.attachProducer(); // 修改BufferState成Dequued状态
  
    mCore->mActiveBuffers.insert(found);            // 把slot放到mActiveBuffers中管理。
    return returnFlags;
}

(3). queuebuffer的流程

queuebuffer是生产者完成对GraphicBuffer的处理之后调用queuebuffer把GraphicBuffer放回mQueue的操作,同时把mBufferState修改成QUEUE状态。

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
 
    sp<IConsumerListener> frameAvailableListener;
    sp<IConsumerListener> frameReplacedListener;
 
    BufferItem item;
    { // Autolock scope
  
        mSlots[slot].mBufferState.queue();  //修改mBufferState状态为QUEUE状态。
 
        //增加mFrameCounter
        ++mCore->mFrameCounter;
        currentFrameNumber = mCore->mFrameCounter;
        mSlots[slot].mFrameNumber = currentFrameNumber;
 
        //给BufferItem赋值
        item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
        item.mSlot = slot;
 
        output->bufferReplaced = false;
        if (mCore->mQueue.empty()) { //如果mQueue为空,就直接把BufferItem push到mQueue尾部。
            mCore->mQueue.push_back(item);
            frameAvailableListener = mCore->mConsumerListener;
        } else {  //如mQueue不为空,需要判断一下last BufferItem是否被替换,如果可以替换就替换,如果不可以替换就直接把BufferItem放到mQueue尾部。
            const BufferItem& last = mCore->mQueue.itemAt(
                    mCore->mQueue.size() - 1);
            if (last.mIsDroppable) {
 
                if (!last.mIsStale) {
                    mSlots[last.mSlot].mBufferState.freeQueued();
 
                    // Don't put the shared buffer on the free list.
                    if (!mSlots[last.mSlot].mBufferState.isShared()) {
                        mCore->mActiveBuffers.erase(last.mSlot);
                        mCore->mFreeBuffers.push_back(last.mSlot);
                        output->bufferReplaced = true;
                    }
                }
 
                mCore->mQueue.editItemAt(mCore->mQueue.size() - 1) = item;
                frameReplacedListener = mCore->mConsumerListener;
            } else {
                mCore->mQueue.push_back(item);
                frameAvailableListener = mCore->mConsumerListener;
            }
        }
        if (frameAvailableListener != nullptr) {
            frameAvailableListener->onFrameAvailable(item);   //调用消费者的onFrameAvailable通知消费者,有queue状态的BufferSlot可以使用。
        } else if (frameReplacedListener != nullptr) {
            frameReplacedListener->onFrameReplaced(item);     //调用消费者的onFrameReplaced通知消费者,有queue状态的BufferSlot可以被替换。
        }
    } // Autolock scope
 
    return NO_ERROR;
}

(4). detachBuffer的流程

detachBuffer主要是对应生产者端的attachbuffer操作,将attachbuffer之后的BufferSlot,放回到mFreeSlots中,并解除对GraphicBuffer的绑定,并通知消费者Buffer释放。

status_t BufferQueueProducer::detachBuffer(int slot) {
 
    sp<IConsumerListener> listener;
    
        mSlots[slot].mBufferState.detachProducer();  //修改BufferState的Dequeued状态成FREE状态
        mCore->mActiveBuffers.erase(slot);           //把slot从mActiveBuffers中移除
        mCore->mFreeSlots.insert(slot);              //把slot加到mFreeSlots中。
        mCore->clearBufferSlotLocked(slot);          //清除slot和Graphicbuffer的绑定关系。
        mCore->mDequeueCondition.notify_all();
        listener = mCore->mConsumerListener;   //把消费者回调的listener赋值给listener
    }
 
    if (listener != nullptr) {
        listener->onBuffersReleased();//调用消费者的listener接口通知消费者Buffer释放
    }
 
    return NO_ERROR;
}

五、总结

BufferQueue的生产消费和BufferSlot状态关系用一张图总结下:


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容