Metal-02-Metal Shading Language

我們今天來大致講一下Metal Shading Language 著色語言規(guī)范蜡峰。看一下平時寫Metal 需要注意些什么~

一臭蚁、Metal Shading Language

1邮偎、定義及作用

Metal著?語? 是?來編寫 3D 圖形渲染邏輯并?計算核?邏輯的??編程語?. 當你使? Metal 框架來完成APP 的實現(xiàn)時則需要使?Metal 編程語?;

Metal 語?使?Clang 和 LLVM 進?編譯處理~

Metal 基于C++ 11.0 語?設(shè)計的,我們主要?來編寫 在 GPU 上執(zhí)?的圖像渲染邏輯代碼 以及 通? 并?計算邏輯代碼;

2棵里、注意點

  1. 對于指針的限制:

    • Metal圖形和并?計算函數(shù)?到的?參數(shù); 如果是指針必須使?地址空間修飾符(device, threadgroup, constant)
    • 不?持函數(shù)指針
    • 函數(shù)名不能出現(xiàn) main
  2. Metal 像素坐標系統(tǒng): Metal中 紋理/幀緩存區(qū)attachment 的像素使?的坐標系統(tǒng)的原點是左上?(與OpenGL里的不同)润文。

3、Metal語? 與 C++ 11.0 的不同之處

雖然 Metal 是基于 C++ 11.0 語?設(shè)計的衍慎,但是有一些 C++ 11.0 中的特性語法转唉,在Metal中 不支持:

  • Lambda 表達式
  • 遞歸函數(shù)調(diào)?
  • 動態(tài)轉(zhuǎn)換操作符
  • 類型識別
  • 對象創(chuàng)建new 和銷毀delete 操作符
  • 操作符 noexcept
  • goto 跳轉(zhuǎn)
  • 變量存儲修飾符register 和 thread_local
  • 虛函數(shù)修飾符
  • 派?類
  • 異常處理
  • C++ 標準庫在Metal 語?中也不可使?

二、基礎(chǔ)數(shù)據(jù)類型

與GLSL語言不同稳捆,float類型后面允許帶f或者F募壕。同樣,half類型后面允許帶h或者H河狐。

1、標量

image
  • 注意 unsigned 修飾的可以簡寫成 u款侵。例如:unsigned char 簡寫 uchar
bool a = true;
char b = 5;
int  c = 15;
//用于表示內(nèi)存空間的大小
size_t d = 1;
ptrdiff_t e = 2;

2、向量

booln 侧纯、charn 新锈、shortn 、intn 眶熬、ucharn 妹笆、ushortn 、uintn 娜氏、halfn 拳缠、floatn(n指的是維度 1~4)

//初始化類型1:直接賦值
bool2 A = {1,0};
float4 pos ={1.0,2.0,3.0,4.0};
//初始化類型2:通過內(nèi)建變量賦值
bool2 A = bool2(1,0);
float4 pos = float4(1.0,2.0,3.0,4.0);

//根據(jù)下標取值
float x = pos[0];
float y = pos[1];

//通過for循環(huán)賦值
float4 VB;
for(int i = 0; i < 4 ; i++)
{
    VB[i] = pos[i] * 2.0f;
}

向量有且只有兩套向量分量(xyzwrgba)用來獲取元素贸弥,但是注意:
可以單套亂序取值窟坐,不能兩套混合使用賦值的時候分量不能重復(fù)绵疲,取值的時候分量可以重復(fù)

//通過向量字母來獲取元素(向量分量)
int4 test = int4(0,1,2,3);
int a = test.x;
int b = test.y;
int c = test.z;
int d = test.w;

int e = test.r;
int f = test.g;
int g = test.b;
int h = test.a;

float4 c;
c.xyzw = float4(1.0f,2.0f,3.0f,4.0f);
c.z = 1.0f;
c.xy = float2(3.0f,4.0f);
c.xyz = float3(3.0f,4.0f,5.0f);


float4 pos = float4(1.0f,2.0f,3.0f,4.0f);
float4 swiz = pos.wxyz;  //swiz = (4.0,1.0,2.0,3.0);
float4 dup = pos.xxyy;  //dup = (1.0f,1.0f,2.0f,2.0f);

//pos = (5.0f,2.0,3.0,6.0)
pos.xw = float2(5.0f,6.0f);

//pos = (8.0f,2.0f,3.0f,7.0f)
pos.wx = float2(7.0f,8.0f);

//pos = (3.0f,5.0f,9.0f,7.0f);
pos.xyz = float3(3.0f,5.0f,9.0f);

pos.xr //非法的哲鸳,不能混合使用

pos.xx = float2(2.0f,3.0f); //非法的,不能連續(xù)重復(fù)賦值

注意盔憨,不要越界

float2 pos;
pos.x = 1.0f; //合法
pos.z = 1.0f; //非法徙菠,只定義了2個元素,取第3個就越界了

float3 pos2;
pos2.z = 1.0f; //合法
pos2.w = 1.0f; //非法般渡,只定義了3個元素懒豹,取第4個就越界了

列舉一下,二維向量驯用、三維向量脸秽、四維向量所有的構(gòu)造方式

//float2類型向量的所有可能的構(gòu)造方式
float2(float x);
float2(float x,float y);
float2(float2 x);

//float3類型向量的所有可能的構(gòu)造的方式
float3(float x);
float3(float x,float y,float z);
float3(float a,float2 b);
float3(float2 a,float b);
float3(float3 x);

//float4類型向量的所有可能構(gòu)造方式
float4(float x);
float4(float x,float y,float z,float w);
float4(float2 a,float2 b);
float4(float2 a,float b,float c);
float4(float a,float2 b,float c);
float4(float a,float b,float2 c);
float4(float3 a,float b);
float4(float a,float3 b);
float4(float4 x);

3、矩陣

halfnxm 蝴乔、floatnxm (nxm分別指的是矩陣的?數(shù)和列數(shù))

//定義一個4x4的矩陣m  注意记餐,4行4列是 m[3][3]
float4x4 m;

//設(shè)置第一行/第一列為1.0f
m[0][0] = 1.0f;

//將第二排的4個值都設(shè)置為0
m[1] = float4(2.0f);

//設(shè)置第三行第四列的元素為3.0f
m[2][3] = 3.0f;

三、紋理類型和采樣器類型

1薇正、紋理類型 Textures

紋理類型是?個句柄(id), 它指向?個?維/?維/三維紋理數(shù)據(jù)片酝。是定義好的枚舉值:

enum class access {
sample , //紋理對象可以被采樣. 采樣?維這是使?或不使?采樣器從紋理中讀取數(shù)據(jù);
read ,   //不使?采樣器, ?個圖形渲染函數(shù)或者?個并?計算函數(shù)可以讀取紋理對象
write    //?個圖形渲染函數(shù)或者?個并?計算函數(shù)可以向紋理對象寫?數(shù)據(jù)
};

//sample 用的最多,可讀可寫可采樣

3種紋理:
texture1d<T, access a = access::sample>
texture2d<T, access a = access::sample>
texture3d<T, access a = access::sample>
T : 數(shù)據(jù)類型 設(shè)定了從紋理中讀取或是向紋理中寫?時的顏?類型. T可以是half, float, short, int 等

代碼示例:


/*
類型 變量 修飾符

類型:二維紋理
     texture2d<float>挖腰,讀取的數(shù)據(jù)類型是float雕沿,沒寫access,默認是sample
     texture2d<float,access::read>猴仑,讀取的數(shù)據(jù)類型是float审轮,讀取的方式是read
     texture2d<float,access::write>,讀取的數(shù)據(jù)類型是float,讀取的方式是write

變量:
    imgA
    imgB
    imgC
修飾符:
    [[texture(0)]] 對應(yīng)紋理0
    [[texture(1)]] 對應(yīng)紋理1
    [[texture(2)]] 對應(yīng)紋理2
 
*/
void foo (  texture2d<float> imgA [[ texture(0) ]] , 
            texture2d<float, access::read> imgB [[ texture(1) ]], 
            texture2d<float, access::write> imgC [[ texture(2) ]]) 
{ 
    ... 
}

2疾渣、采樣器類型 Samplers

采取器類型決定了如何對?個紋理進?采樣操作. 在Metal 框架中有?個對應(yīng)著?器語?的采樣器的對象 MTLSamplerState 這個對象作為圖形渲染著?器函數(shù)參數(shù)或是并?計算函數(shù)的參數(shù)傳遞;

image
  • 從紋理中采樣時,紋理坐標是否需要歸?化
enum class coord { normalized, pixel };
  • 紋理采樣過濾?式, 放?/縮?過濾模式
enum class filter { nearest, linear };
  • 設(shè)置紋理采樣的縮?過濾模式
enum class min_filter { nearest, linear };
  • 設(shè)置紋理采樣的放?過濾模式
enum class mag_filter { nearest, linear };
  • 設(shè)置所有的紋理坐標的尋址模式
enum class address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
  • 設(shè)置紋理s,t,r坐標的尋址模式;
enum class s_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };

enum class t_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };

enum class r_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
  • 設(shè)置紋理采樣的mipMap過濾模式(如果是none,那么只有?層紋理?效)
enum class mip_filter { none, nearest, linear };

注意: 在Metal 程序中初始化的采樣器必須使? constexpr修飾符聲明篡诽。
代碼示例:

//設(shè)置歸一化、設(shè)置尋址模式榴捡、過濾方式
constexpr sampler s ( coord::pixel,
                      address::clamp_to_zero, 
                      filter::linear );
                      
//如果都是用默認也可以不寫杈女,或者只寫需要設(shè)定的
constexpr sampler a ( coord::normalized ); 
constexpr sampler b ( address::repeat );

四、函數(shù)修飾符

Metal 有以下3種函數(shù)修飾符:kernel吊圾、vertex达椰、fragment

1项乒、kernel

表示該函數(shù)是?個數(shù)據(jù)并?計算著?函數(shù). 它可以被分配在?維/?維/三維線程組中去執(zhí)?

kernel void foo(...) 
{ 
    ... 
}

2砰碴、vertex

表示該函數(shù)是?個頂點著?函數(shù) , 它將為頂點數(shù)據(jù)流中的每個頂點數(shù)據(jù)執(zhí)??次然后為每個頂 點?成數(shù)據(jù)輸出到繪制管線;

3、fragment

表示該函數(shù)是?個?元著?函數(shù), 它將為?元數(shù)據(jù)流中的每個?元 和其關(guān)聯(lián)執(zhí)??次然后 將每個?元?成的顏?數(shù)據(jù)輸出到繪制管線中;

注意:

  • 被上面三種修飾符修飾的函數(shù)中板丽,不能調(diào)用 同樣被這三個修飾符修飾的函數(shù),否則會直接編譯失敗趁尼“<睿可以調(diào)用普通函數(shù)。
  • 使?kernel 修飾的函數(shù). 其返回值類型必須是 void 類型
  • 只有圖形著?函數(shù)才可以被 vertex 和 fragment 修飾酥泞,返回值類型可以辨認出它是為 頂點做計算還是為每像素做計算
  • 圖形著?函數(shù)的返回值可以為 void , 但是這也就意味著該函數(shù)不產(chǎn)?數(shù) 據(jù)輸出到繪制管線; 這是?個?意義的動作

五砚殿、地址空間修飾符(?于變量或者參數(shù))

地址空間修飾符有4種: device、threadgrounp芝囤、constant似炎、thread

使? 地址空間修飾符 來表示?個函數(shù)變量或者參數(shù)變量 被分配于那??內(nèi)存區(qū)域

  • 所有的著?函數(shù)(vertex, fragment, kernel)的參數(shù),如果是指針或是引?, 都必須帶有地址空間修飾符號
  • 對于圖形著?器函數(shù), 其指針或是引?類型的參數(shù)必須定義為 device 或是 constant 地址空間
  • 對于并?計算著?函數(shù), 其指針或是引?類型的參數(shù)必須定義為 device 或是 threadgrounp 或是 constant 地址空間

1、device Address Space(設(shè)備地址空間)

device(設(shè)備地址空間)指向設(shè)備內(nèi)存池分配出來的緩存對象(這里設(shè)備指的的顯存悯姊,GPU)羡藐。它是可讀也是可寫的。?個緩存對象可 以被聲明成?個標量悯许、向量仆嗦、?戶?定義結(jié)構(gòu)體或者結(jié)構(gòu)體的指針

  • 放在顯存中,是為了讀取更快
  • 紋理對象先壕,不用device修飾瘩扼,也會默認放在 顯存中
//1.修飾指針變量。表示:這個 四維向量顏色 的指針 放在顯存中
device float4 *color;
 
//2垃僚、修飾結(jié)構(gòu)體指針集绰。表示:把這個結(jié)構(gòu)體的指針 放在顯存中
struct Foo { 
    float a[3]; 
    int b[2]; 
};

device Foo *my_info;

2、threadgrounp Address Space 線程組地址空間

threadgrounp(線程組地址空間)?于為 并?計算著?函數(shù)分配內(nèi)存變量谆棺。 這些變量被?個線程組的所有線程共享. 在線 程組地址空間分配的變量不能被?于圖形繪制著?函數(shù)(頂點著?函數(shù), ?元著?函數(shù))栽燕,也就是在頂點、 ?元著?函數(shù)中不能使用threadgrounp修飾變量

在并?計算著?函數(shù)中, 在線程組地址空間分配的變量為?個線程組使?, 聲明周期和線程組相同

/*
傳入?yún)?shù):
在線程組地址空間分配一個浮點類型變量a的指針
*/
kernel void my_func(threadgroup float *a [[ threadgroup(0) ]], ...)
{
    //在線程組地址空間分配一個浮點類型變量x
    threadgroup float x;
    
    //在線程組地址空間分配一個10個浮點類型數(shù)的數(shù)組y;
    threadgroup float y[10];
    
}

3、constant Address Space 常量地址空間

  • constant(常量地址空間)指向的緩存對象也是從設(shè)備內(nèi)存池(顯存中)分配存儲, 但是它是只讀的
  • 在程序域中纫谅,constant 修飾的變量炫贤,必須在聲明的時候就初始化并賦值
  • 在程序域中,constant 修飾的變量的生命周期和程序一樣付秕,在程序中的并?計算著?函數(shù)或者圖形繪制著?函數(shù)調(diào)?, 但是 constant 的值會保持不變(也就是一旦初始化就是常量兰珍,不允許修改

注意:constant修飾的變量,如果不賦值询吴、或者后面進行修改掠河,都會產(chǎn)生編譯錯誤

//這樣聲明是正確的 ?
constant float samples[] = { 1.0f, 2.0f, 3.0f, 4.0f };

//但是,一旦修改猛计,就編譯錯誤了 ?
sampler[4] = {3,3,3,3};

//這樣聲明是錯誤的 ?  沒有賦值唠摹,直接編譯失敗
constant float a;

4、thread Address Space 線程地址空間

thread(線程地址空間)指向每個線程準備的地址空間奉瘤,這個線程的地址空間定義的變量在其他線程不可?勾拉,在 圖形繪制著?函數(shù)或者并?計算著?函數(shù)中聲明的變量thread 地址空間分配

  • threadgroup修飾的變量,線程之間可以共享盗温。thread修飾的就不行藕赞,
  • 在著色函數(shù)中,不能用threadgroup卖局, 可以用thread
kernel void my_func(...)
{
    //在線程空間分配空間給x,p
    float x;
    thread float p = &x;
}

六斧蜕、傳遞修飾符(函數(shù)參數(shù)與變量的屬性修飾符)

在圖形繪制 或者 并行計算著色器函數(shù) 的 輸入輸出都是通過參數(shù)傳遞的。除了常量地址空間變量和程序域定義的采樣器之外, 其他參數(shù)修飾的可以是如下之一砚偶,有以下5種屬性修飾符:

  • device buffer :設(shè)備緩存批销,一個指向設(shè)備地址空間的任意數(shù)據(jù)類型的指針/引用
  • constant buffer :常量緩存,一個指向常量地址空間的任意數(shù)據(jù)類型的指針/引用
  • texture :紋理對象
  • sampler :采樣器對象
  • threadGroup :在線程組中供線程共享的緩存

那么染坯,為什么需要屬性修飾符均芽?

  • 參數(shù)表示資源的定位,可以理解為端口酒请,相當于OpenGl ES中的location
  • 在固定管線和可編程管線進行內(nèi)建變量的傳遞
  • 將數(shù)據(jù)沿著渲染管線從頂點函數(shù)傳遞到片元函數(shù)

對于每個著色函數(shù)來說骡技,一個修飾符是必須指定的,它用來設(shè)置一個緩存羞反、紋理布朦、采樣器的位置,傳遞修飾符對應(yīng)的寫法如下:

  • device buffer ---> [[buffer(index)]]
  • constant buffer ---> [[buffer(index)]]
  • texture ---> [[texture(index)]]
  • sampler ---> [[sampler(index)]]
  • threadGroup ---> [[threadGroup(index)]]
index 可以由開發(fā)者來指定昼窗,是一個unsigned interger類型的值是趴,
表示了一個緩存、紋理澄惊、采樣器參數(shù)的位置唆途,即在函數(shù)參數(shù)索引表中的位置富雅。
屬性修飾符一般放在變量名后面。


在代碼中如何表現(xiàn):
 1.已知條件:device buffer(設(shè)備緩存)/constant buffer(常量緩存)
 代碼表現(xiàn):[[buffer(index)]]
 解讀:不變的buffer ,index 可以由開發(fā)者來指定.
 
 2.已知條件:texture Object(紋理對象)
 代碼表現(xiàn): [[texture(index)]]
 解讀:不變的texture ,index 可以由開發(fā)者來指定.
 
 3.已知條件:sampler Object(采樣器對象)
 代碼表示: [[sampler(index)]]
 解讀:不變的sampler ,index 可以由開發(fā)者來指定.
 
 4.已知條件:threadgroup Object(線程組對象)
 代碼表示: [[threadgroup(index)]]
 解讀:不變的threadgroup ,index 可以由開發(fā)者來指定.

代碼示例

//并行計算著色器函數(shù)肛搬,屬性修飾符"[[buffer(index)]]" 為著色函數(shù)參數(shù)設(shè)定了緩存的位置
/*
kernel :并行計算函數(shù)修飾符
void :函數(shù)的返回值 (kernel修飾没佑,返回值必須是void)
add_vectors:函數(shù)名
const device float4 *inA [[buffer(0)]]:
    const :修飾的這個變量不能修改
    device:修飾的這個變量,放在顯存中
    float4:這個變量的類型:四維向量
    inA:變量名
    [[buffer(0)]]:表示這個變量在 buffer中對應(yīng)的0這個id
    
thread_position_in_grid:用于表示當前節(jié)點在多線程網(wǎng)格中的位置,并不需要開發(fā)者傳遞温赔,是Metal自帶的蛤奢。
*/
kernel void add_vectors(const device float4 *inA [[ buffer(0) ]],
                        const device float4 *inB [[ buffer(1) ]],
                        device float4 *out [[ buffer(2) ]],
                        uint id [[ thread_position_in_grid ]])
{
    out[id] = inA[id] + inB[id];
}

七、常見的內(nèi)建變量屬性修飾符

  1. [[vertex_id]] :頂點id標識符陶贼,并不由開發(fā)者傳遞
  2. [[position]]
    • 在頂點函數(shù)中啤贩,表示頂點信息,類型是float4
    • 在片元函數(shù)中拜秧,表示片元的窗口相對坐標(x,y,z,w),即像素在屏幕上的位置信息
  3. [[point_size]] :點的大小痹屹,float類型
  4. [[color(m)]] :顏色,m在編譯前必須確定
  5. [[stage_in]] :由頂點函數(shù)輸出 經(jīng)過光柵化生成 傳入片元函數(shù)的數(shù)據(jù)枉氮。 需要注意:無論在頂點還是片元函數(shù)中志衍,只能聲明一個被stage_in修飾的參數(shù)。這個參數(shù)也可以是一個結(jié)構(gòu)體聊替,結(jié)構(gòu)體內(nèi)的成員類型可以是整型/浮點的標量/向量
//定義了片元輸入的結(jié)構(gòu)體足画,
struct MyFragmentOutput {
      // color attachment 0 顏色附著點0
     float4 clr_f [[color(0)]]; 
     // color attachment 1 顏色附著點1
     int4 clr_i [[color(1)]]; 
     // color attachment 2 顏色附著點2
     uint4 clr_ui [[color(2)]]; 
};

fragment MyFragmentOutput my_frag_shader( ... ) 
{
    MyFragmentOutput f;
    ....
    f.clr_f = ...;
    ....
    return f; 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市佃牛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌医舆,老刑警劉巖俘侠,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蔬将,居然都是意外死亡爷速,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門霞怀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惫东,“玉大人,你說我怎么就攤上這事毙石×冢” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵徐矩,是天一觀的道長滞时。 經(jīng)常有香客問我,道長滤灯,這世上最難降的妖魔是什么坪稽? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任曼玩,我火速辦了婚禮,結(jié)果婚禮上窒百,老公的妹妹穿的比我還像新娘黍判。我一直安慰自己,他們只是感情好篙梢,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布顷帖。 她就那樣靜靜地躺著,像睡著了一般庭猩。 火紅的嫁衣襯著肌膚如雪窟她。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天蔼水,我揣著相機與錄音震糖,去河邊找鬼。 笑死趴腋,一個胖子當著我的面吹牛吊说,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播优炬,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼颁井,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蠢护?” 一聲冷哼從身側(cè)響起雅宾,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葵硕,沒想到半個月后眉抬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡懈凹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年蜀变,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片介评。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡库北,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出们陆,到底是詐尸還是另有隱情寒瓦,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布坪仇,位于F島的核電站孵构,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏烟很。R本人自食惡果不足惜颈墅,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一蜡镶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恤筛,春花似錦官还、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至煎殷,卻和暖如春屯伞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背豪直。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工劣摇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弓乙。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓末融,卻偏偏與公主長得像,于是被迫代替她去往敵國和親暇韧。 傳聞我的和親對象是個殘疾皇子勾习,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345