圖層性能之離屏渲染、柵格化、回收池

CoreAnimationXmind.png

這是 Core Animation 的系列文章,介紹了 Core Animation 的用法,以及如何進(jìn)行性能優(yōu)化。

  1. CoreAnimation基本介紹
  2. CGAffineTransform和CATransform3D
  3. CALayer及其各種子類
  4. CAAnimation:屬性動畫CABasicAnimation、CAKeyframeAnimation以及過渡動畫、動畫組
  5. 圖層時間CAMediaTiming
  6. 計(jì)時器CADisplayLink
  7. 影響動畫性能的因素及如何使用 Instruments 檢測
  8. 圖像IO之圖片加載、解碼,緩存
  9. 圖層性能之離屏渲染、柵格化、回收池

上一篇文章圖像IO之圖片加載、解碼,緩存介紹了如何高效加載、繪制圖片,避免影響幀率。這篇文章著重介紹圖層樹,以實(shí)現(xiàn)更好性能。

1. 隱式繪制 Inexplicit Drawing

圖層的 backing image 可以使用 Core Graphics 繪制,或?yàn)?code>contents屬性賦值圖片,或在離屏的CGContext提前渲染。之前的文章已經(jīng)介紹過了這些場景的優(yōu)化,但還可以通過以下三種方式優(yōu)化:

  • 修改圖層屬性。
  • 使用特殊視圖。
  • 使用特定圖層子類。

應(yīng)當(dāng)了解什么情況下使用何種方式,并盡可能避免使用軟件繪制。

1.1 柵格化 Rasterization

在之前的文章中,為了解決半透明圖層重疊、復(fù)雜圖層樹的性能問題,已經(jīng)使用過CALayershouldRasterize屬性。shouldRasterize默認(rèn)值為 false。

1.1.1 性能特點(diǎn)
  • 當(dāng)被設(shè)置為 true 時,圖層被 GPU 離屏渲染成圖片,圖片會被緩存并用來替換圖層及其子圖層。但首次使用時需時間來生成圖片,并占用額外內(nèi)存。
  • 內(nèi)容更新時,再次離屏渲染圖片。
  • 緩存大小被限制為2.5倍屏幕大小,
  • 緩存超過100ms沒有使用會被自動丟棄。
1.1.2 用途
  • 靜態(tài)內(nèi)容想要避免重復(fù)繪制特殊效果,例如圓角、陰影等。因?yàn)橐坏﹥?nèi)容發(fā)生變化(如 resize、動畫),之前處理得到的緩存就失效了。如果頻繁發(fā)生變化,就又回到了每一幀都需要離屏渲染的場景,緩存占用的內(nèi)存只會讓性能變得更糟。
  • 想要避免重復(fù)繪制復(fù)雜層級結(jié)構(gòu)。如果有很多子圖層,或子圖層有復(fù)雜顯示效果,柵格化性能遠(yuǎn)高于每幀重繪。

使用 Debug > View Debugging > Rendering > Color Hits Green and Misses Red 檢測是否使用了緩存圖片。如果緩存的圖片需要不斷重新生成,該選項(xiàng)會使用紅色標(biāo)記重繪部分。

2. 離屏渲染 Offscreen Rendering

在屏幕中顯示內(nèi)容時,需一塊至少與屏幕像素?cái)?shù)據(jù)量一樣大的 frame buffer,作為像素?cái)?shù)據(jù)存儲區(qū)域,而這也是 GPU 存儲渲染結(jié)果的地方。如果因某些限制,無法把渲染結(jié)果直接寫入 frame buffer,需先暫存到單獨(dú)的一塊內(nèi)存區(qū)域,之后再寫入 frame buffer,這個過程被稱為離屏渲染 Offscreen Rendering

渲染結(jié)果先經(jīng)過了離屏 buffer,再到 frame buffer。如下圖所示:

CAOffscreenBuffer.png

2.1 CPU“離屏渲染”

如果在UIView中實(shí)現(xiàn)了draw(_:)方法,即使函數(shù)體內(nèi)部沒有任何代碼,系統(tǒng)也會為其分配一塊內(nèi)存,等待 Core Graphics 可能的繪制操作。

Core Graphics、CoreText 的任何繪制方法,都會分配單獨(dú)內(nèi)存,不會直接繪制到 frame buffer。因?yàn)?CPU 不擅長渲染,我們就會認(rèn)為需要盡量避免離屏渲染,但根據(jù) Apple 工程師的說法,CPU 渲染并非真正意義上的離屏渲染。開啟 Xcode 中的 Color Offscreen-Rendered Yellow 后,使用draw(_:)繪制的區(qū)域并不會被標(biāo)記為黃色,從另一方面也說明了 Xcode 也不認(rèn)為這屬于離屏渲染。

2.2 畫家算法

渲染工作主要由獨(dú)立進(jìn)程中的 render server 完成。對于每一層 layer,render server 會遵循畫家算法,把各層按照深度排序,由深到淺依次輸出到 frame buffer,后一次覆蓋前一層。

CAPainter.png

上層會覆蓋底層,被遮蓋部分像素?cái)?shù)據(jù)永久丟失。此時不能通過修改當(dāng)前層的某一部分,讓底下的層重新顯示出來。

如果能在 frame buffer 之外另開啟一塊內(nèi)存,把待處理的 layer 先畫上去,然后在這塊臨時區(qū)里執(zhí)行擦除、修改工作,處理完畢再寫回到 frame buffer,得到最終結(jié)果。雖然這種方法需要額外空間,但得到了更大的靈活性。

2.3 GPU 離屏渲染

上面提到的 frame buffer 之外的內(nèi)存,稱為離屏 buffer,整個過程就是離屏渲染。對于每一層 layer,我們肯定希望找到單次遍歷就能完成渲染的算法,不然就需要申請一塊離屏 buffer,借助臨時中轉(zhuǎn)區(qū)完成一些修改、剪切操作。單獨(dú)開辟離屏 buffer 是一種很昂貴的操作。

離屏渲染并不意味著軟件繪制,但一定是在離屏 context 由 CPU 或 GPU 渲染。以下圖層屬性會引起離屏渲染:

  • cornerRadiusmasksToBounds一起使用。
  • mask。
  • 陰影。

離屏渲染和開啟光柵化有些像,但離屏渲染開銷并沒有光柵化那么大,子圖層也不受影響,也不會緩存渲染結(jié)果,因此不會產(chǎn)生長期內(nèi)存開銷。太多圖層離屏渲染會明顯影響性能。

2.4 避免離屏渲染方案

如果圖層需要 offscreen rendering,且圖層內(nèi)容沒有變化,可以開啟光柵化優(yōu)化圖層性能;如果圖層需要 offscreen rendering,且圖層內(nèi)容有變化,可以使用CAShapeLayercontentsCentershadowPath實(shí)現(xiàn)相似效果,同時避免了離屏渲染。

2.4.1 CAShapeLayer

cornerRadiusmasksToBounds本身不會引起性能問題,只有一起使用時才會引起 offscreen rendering。

有時需要顯示圓角矩形并裁減超出邊界的子圖層,但有時不需要裁減圓角,這時使用CAShapeLayer可以避免離屏渲染問題。

使用UIBezierPathinit(roundedRect:byRoundingCorners:cornerRadii:)創(chuàng)建左上角、右下角圓角的矩形:

        let blueLayer = CAShapeLayer()
        blueLayer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        blueLayer.fillColor = UIColor.blue.cgColor
        blueLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 100, height: 100), byRoundingCorners: [.topLeft, .bottomRight], cornerRadii: CGSize(width: 10, height: 10)).cgPath
        view.layer.addSublayer(blueLayer)
2.4.2 拉伸圖片

另一種創(chuàng)建圓角矩形方式是使用圓角圖片,賦值給contents屬性,并結(jié)合contentsCenter創(chuàng)建可拉伸圖片。理論上來說,這種方式渲染速度比CAShapeLayer速度快。一個可拉伸圖片需要18個三角形(一個圖片是由一個3*3網(wǎng)格渲染而成),一條順滑曲線需要很多三角形。

        let blueLayer = CAShapeLayer()
        blueLayer.bounds = CGRect(x: 0, y: 0, width: 200, height: 200)
        blueLayer.contentsCenter = CGRect(x: 0.5, y: 0.5, width: 0.0, height: 0.0)
        blueLayer.contentsScale = UIScreen.main.scale
        blueLayer.contents = UIImage(named: "Rounded")?.cgImage
        view.layer.addSublayer(blueLayer)

拉伸圖片可以創(chuàng)建任意邊框效果,而無需額外開銷。

2.4.3 shadowPath

如果圖層是矩形或圓角矩形,不含部分透明、子圖層,可以很簡單的創(chuàng)建陰影路徑,進(jìn)而簡化 Core Animation 繪制陰影的計(jì)算工作,也避免了離屏渲染。

如果陰影形狀復(fù)雜,使用圖片作為陰影性能可能更好。

2.4.4 Group Opacity

allowsGroupOpacity屬性是布爾類型,指示是否允許圖層獨(dú)立于父圖層進(jìn)行合成。

allowsGroupOpacity值為true、opacity值小于1.0時,圖層可以獨(dú)立于父圖層進(jìn)行合成,顯示結(jié)果更逼真,特別是圖層包含不透明組件時,但也更耗費(fèi)性能。

默認(rèn)從 bundle 的 Info.plist 文件讀取UIViewGroupOpacity屬性。如果沒有讀取到,則使用默認(rèn)值 true。

當(dāng)opacity小于1.0時,會觸發(fā)離屏渲染。設(shè)置為 false 可以避免這一情況。

將一對紅色、藍(lán)色圖層重疊在一起,設(shè)置父圖層opacity為0.5,并復(fù)制一份作為對比。左側(cè)開啟 group opacity,右側(cè)關(guān)閉。如下所示:

        let redLayer1 = CALayer()
        redLayer1.frame = CGRect(x: view.bounds.size.width / 2 - 130, y: 100, width: 100, height: 200)
        redLayer1.backgroundColor = UIColor.red.cgColor
        redLayer1.opacity = 0.5
        redLayer1.allowsGroupOpacity = true
        view.layer.addSublayer(redLayer1)
        
        let blueLayer1 = CALayer()
        blueLayer1.frame = CGRect(x: 0, y: 0, width: 50, height: 100)
        blueLayer1.backgroundColor = UIColor.blue.cgColor
        blueLayer1.opacity = 0.8
        redLayer1.addSublayer(blueLayer1)
        
        // 關(guān)閉 group opacity
        let redLayer2 = CALayer()
        redLayer2.frame = CGRect(x: view.bounds.size.width / 2 + 30, y: 100, width: 100, height: 200)
        redLayer2.backgroundColor = UIColor.red.cgColor
        redLayer2.opacity = 0.5
        redLayer2.allowsGroupOpacity = false
        view.layer.addSublayer(redLayer2)
        
        let blueLayer2 = CALayer()
        blueLayer2.frame = CGRect(x: 0, y: 0, width: 50, height: 100)
        blueLayer2.backgroundColor = UIColor.blue.cgColor
        blueLayer2.opacity = 0.8
        redLayer2.addSublayer(blueLayer2)

打開 Color Offscreen-Rendered Yellow 后如下:

CAGroupOpacity.png

可以看到左側(cè)開啟 group opacity 的圖層進(jìn)行了離屏渲染。

如果用不到 group opacity,永遠(yuǎn)設(shè)置為 false。

3. 混合和重繪

GPU 每幀可渲染像素?cái)?shù)量是有限制的,稱為 fill rate。如果由于圖層重疊,導(dǎo)致同一位置重繪多次,可能導(dǎo)致掉幀。

GPU 不會繪制完全被其他圖層覆蓋的像素,但計(jì)算圖層是否被覆蓋會耗費(fèi)處理器資源。只有在必要的時候才使用透明度。下面方案可以提高性能:

  • 設(shè)置backgroundColor為固定、不透明顏色。
  • 設(shè)置視圖的opaque為 true。

圖層不透明后,底層圖層不會對顯示效果有任何作用,避免了混合和重繪。

除非特別需要,應(yīng)避免圖片 alpha 透明度。如果圖片顯示在固定背景色、靜態(tài)圖之上,且背景色、靜態(tài)圖不會隨 foreground 移動,應(yīng)填充圖片背景,避免運(yùn)行時混合。

使用UILabel時,白色或其他純色背景比透明背景更容易渲染。

通過使用shouldRasterize屬性,可以把靜態(tài)圖層樹壓縮為一張圖片,無需每幀時混合,避免混合、重繪的開銷。

4. 減少圖層數(shù)量

初始化、預(yù)處理、打包并使用 IPC 發(fā)送到 render server,及轉(zhuǎn)換至 OpenGL/Metal 等都會產(chǎn)生開銷,這些限制了屏幕可顯示圖層數(shù)量上限。

上限數(shù)量隨硬件、圖層類型、內(nèi)容、屬性而已,但一般圖層數(shù)量達(dá)到成百上千后,即使圖層沒進(jìn)行任何設(shè)置也會開始產(chǎn)生性能問題。

4.1 3D 圖層矩陣

對圖層進(jìn)行優(yōu)化前,先確保屏幕上不顯示的圖層不要初始化、添加到屏幕中。圖層可能由于以下原因不顯示:

  • 位于屏幕、父圖層可見區(qū)域之外。
  • 被其他不透明圖層覆蓋。
  • 完全透明。

Core Animation 可以很好的處理不可見圖層,但你的代碼能更早(比如創(chuàng)建前)的解決此類問題,進(jìn)而避免初始化、配置圖層的開銷。

下面代碼創(chuàng)建了滑動的3D圖層矩陣,圖層只簡單設(shè)置了顏色。

    let width = 10
    let height = 10
    let depth = 10
    let size = 100
    let spacing: CGFloat = 150
    let cameraDistance: CGFloat = 500.0
    
    var scrollView = UIScrollView()
    
    private func test3DMatrixLayer() {
        scrollView.frame = view.bounds
        scrollView.backgroundColor = .gray
        scrollView.contentSize = CGSize(width: spacing * CGFloat(width - 1), height: CGFloat(height - 1) * spacing)
        view.addSubview(scrollView)
        
        var transform = CATransform3DIdentity
        transform.m34 = -1.0 / cameraDistance
        scrollView.layer.sublayerTransform = transform
        
        // Create layers
        for z in 0...(depth-1) {
            for y in 0...height {
                for x in 0...width {
                    // Create layer
                    let layer = CALayer()
                    layer.frame = CGRect(x: 0, y: 0, width: size, height: size)
                    layer.position = CGPoint(x: CGFloat(x) * spacing, y: CGFloat(y) * spacing)
                    layer.zPosition = -CGFloat(z) * spacing
                    
                    // Set background color
                    layer.backgroundColor = UIColor(white: 1-CGFloat(z)*(1.0/CGFloat(depth)), alpha: 1.0).cgColor
                    
                    // Attach to scroll view
                    scrollView.layer.addSublayer(layer)
                }
            }
        }
        
        print("Displayed:\(depth * height * width)")
    }

效果如下:

CA3DMatrixLayer.png

width、height、depth常量控制圖層數(shù)量。在這個demo中,有10*10*10=1000個圖層,其中幾百個可見。

如果增加width、height常量為100,即共十萬個圖層,app 性能會立即降下來。但屏幕中可見圖層數(shù)量并未改變,也就是并未進(jìn)行額外繪制工作,app 性能下降是由于初始化圖層、計(jì)算圖層位置導(dǎo)致。

由于圖層是均勻分布在網(wǎng)格中,可以計(jì)算出當(dāng)前顯示的圖層,也就沒有必要初始化、計(jì)算不可見圖層位置。UITableViewUICollectionView使用了類似機(jī)制,只初始化當(dāng)前可見的 cell。此外,還會復(fù)用出隊(duì)的cell。

4.2 對象回收 Object Recycling

處理大量視圖、圖層時,可以通過對象回收減少性能消耗。UITableViewCell、UICollectionViewCellMKMapView的圖釘都用了回收機(jī)制。

Object recycling 的機(jī)制是創(chuàng)建一個相似對象池,當(dāng)某個實(shí)例不再使用時,添加到回收池;當(dāng)需要新實(shí)例時,從回收池取出,如果回收池沒有實(shí)例,則直接創(chuàng)建。

使用回收池可以避免創(chuàng)建、銷毀對象的開銷,并且可以避免為相似實(shí)例重復(fù)賦值。

使用回收池重構(gòu) demo:

    let width = 100
    let height = 100
    let depth = 10
    let size: CGFloat = 100.0
    let spacing: CGFloat = 150.0
    let cameraDistance: CGFloat = 500.0
    
    var scrollView = UIScrollView()
    var recyclePool = NSMutableSet()
    
    override func viewWillLayoutSubviews() {
        updateLayers()
    }
    
    // 創(chuàng)建3D圖層矩陣
    private func test3DMatrixLayer() {
        scrollView.frame = view.bounds
        scrollView.backgroundColor = .gray
        scrollView.contentSize = CGSize(width: spacing * CGFloat(width - 1), height: CGFloat(height - 1) * spacing)
        scrollView.delegate = self
        view.addSubview(scrollView)
        
        var transform = CATransform3DIdentity
        transform.m34 = -1.0 / cameraDistance
        scrollView.layer.sublayerTransform = transform
    }
    
    // MARK: - 循環(huán)使用已創(chuàng)建的圖層
    
    private func perspective(_ z: CGFloat) -> CGFloat {
        return cameraDistance / (z + cameraDistance)
    }
    
    func updateLayers() {
        // Calculate clipping bounds
        var bounds = scrollView.bounds
        bounds.origin = scrollView.contentOffset
        bounds = bounds.insetBy(dx: -size/2, dy: -size/2)
        
        // Add existing layers to pool
        recyclePool.addObjects(from: scrollView.layer.sublayers ?? [])
        
        // Disable animation
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        
        // Create layers
        var recycled = 0
        var visibleLayers = [CALayer]()
        for z in (0...(depth-1)).reversed() {
            // Increase bounds size to compensate for perspective
            var adjusted = bounds
            adjusted.size.width /= perspective(CGFloat(z)*spacing)
            adjusted.size.height /= perspective(CGFloat(z)*spacing)
            adjusted.origin.x -= (adjusted.size.height - bounds.size.width) / 2
            adjusted.origin.y -= (adjusted.size.height - bounds.size.height) / 2
            
            for y in 0...height {
                // Check if vertically outside visible rect
                
                if CGFloat(y) * spacing < adjusted.origin.y || CGFloat(y)*spacing >= adjusted.origin.y + adjusted.size.height {
                    continue
                }
                
                for x in 0...width {
                    // Check if horizontally outside visible rect
                    if CGFloat(x)*spacing < adjusted.origin.x || CGFloat(x) * spacing >= adjusted.origin.x + adjusted.size.width {
                        continue
                    }
                    
                    // Recycle layer if available
                    var layer = recyclePool.anyObject() as? CALayer
                    if layer != nil {
                        recycled += 1
                        recyclePool.remove(layer)
                    } else {
                        // Otherwise create a new one
                        layer = CALayer()
                        layer?.frame = CGRect(x: 0, y: 0, width: size, height: size)
                    }
                    
                    // Set position
                    layer?.position = CGPoint(x: CGFloat(x)*spacing, y: CGFloat(y)*spacing)
                    layer?.zPosition = -CGFloat(z)*spacing
                    
                    // Set background color
                    layer?.backgroundColor = UIColor(white: 1-CGFloat(z)*(1.0/CGFloat(depth)), alpha: 1).cgColor
                    
                    // Attach to scroll view
                    visibleLayers.append(layer!)
                }
            }
        }
        
        // Update layers
        scrollView.layer.sublayers = visibleLayers
        
        print("Displayed:\(visibleLayers.count)/\(depth*height*width)")
    }

extension LayerPerformanceViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        updateLayers()
    }
}

這個示例中只有一種類型圖層對象,系統(tǒng)框架使用標(biāo)志符區(qū)分不同回收池。

使用回收池之前,每次都初始化圖層,無需使用CATransaction禁用動畫。開啟回收后,對象可能是被回收的,修改圖層屬性會觸發(fā)隱式動畫,因此需先禁用動畫。

4.3 Core Graphics 繪制

除了只創(chuàng)建屏幕中可見圖層,還可以進(jìn)一步減少圖層數(shù)量。例如,如果使用了多個UILabel、UIImageView顯示靜態(tài)內(nèi)容,可以使用draw(_:)將整個視圖層級繪制到一個視圖中。

雖然 GPU 渲染速度比 CPU 快,也無需占用額外內(nèi)存。但當(dāng)性能瓶頸是圖層數(shù)量時,軟件繪制通過減少初始化圖層數(shù)量和圖層樹層級,也能夠提高整體性能。

4.4 render(in:)

雖然有時使用draw(_:)手動繪制內(nèi)容可以提高性能,但要放棄UIView的很多便捷性,如 Interface Builder、Auto layout等。

幸運(yùn)的是不是必須使用draw(_:)。當(dāng)有大量圖層時,只有圖層添加到了圖層樹,才會被發(fā)送給 render tree,此時才會對渲染產(chǎn)生性能影響。

通過CALayerrender(in:)方法,可以將圖層樹繪制到 Core Graphics 的 context,并將結(jié)果作為圖片輸出。shouldRasterize柵格化圖層時,圖層必須添加到圖層樹中。使用render(in:)繪制時圖層無需添加到圖層樹中。

使用render(in:)后,圖層內(nèi)容發(fā)生改變時需開發(fā)者進(jìn)行處理,而shouldRasterize會自動緩存、檢查緩存是否失效。但render(in:)生成圖片后可以減少后續(xù)工作,Core Animaiton 無需維護(hù)復(fù)雜圖層樹。

總結(jié)

CALayerPerformance.png

這篇文章介紹了使用 Core Animation 圖層可能遇到的性能瓶頸,如離屏渲染、混合和重繪、圖層數(shù)量太對。并提供了如下多種解決方案。

  • shouldRasterize將圖層渲染為一張圖片,緩存起來使用。

  • 使用CAShapeLayer、圖片創(chuàng)建圓角。

  • 使用shadowPath創(chuàng)建陰影。

  • 盡可能不使用不透明圖層。

  • 只創(chuàng)建當(dāng)前屏幕可見圖層。

  • 添加圖層到回收池,避免不必要的創(chuàng)建、釋放。

  • 使用 Core Graphics 將復(fù)雜視圖繪制到一個視圖中。

  • 使用render(_:)將圖層樹繪制為圖片。

Demo名稱:CoreAnimation
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/CoreAnimation

上一篇:圖像IO之圖片加載、解碼,緩存

參考資料:

  1. 關(guān)于離屏渲染的深入研究
  2. Advanced Graphics and Animations for iOS Apps Presentation Slides
  3. Advanced Graphics and Animations for iOS Apps Transcript
  4. How to make a UIView's subviews' alpha change according to it's parent's alpha?
  5. A Performance-minded take on iOS design

歡迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/圖層性能之離屏渲染、柵格化、回收池.md

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

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