【原创】Linux环境下的图形系统和AMD R600显卡编程(11)——R600指令集
1 低級著色語言tgsi
OpenGL程序使用GLSL語言對可編程圖形處理器進行編程,GLSL語言(以下高級著色語言就是指GLSL)是語法類似C的高級語言,在GLSL規范中,GLSL語言被先翻譯成教低級的類匯編語言,然后被翻譯成硬件特定的指令集。OpenGL體系管理委員會于2002年6月和2002年9月分別通過了兩個官方擴展:ARB_VERTEX_PROGRAM與ARB_FRAGMENT_PROGRAM來統一對低級著色語言的支持,GLSL語言被編譯成針對這兩個擴展的低級著色語言(因此這兩個擴展可以看成是GLSL運行的虛擬機),顯卡廠商的驅動將低級著色語言翻譯成GPU指令。這兩個擴展的1.0版本分別是arbvpl0和arbfpl0,這兩個擴展的2.0版本分別是arbvp20和arbfp2。
圖1
目前,在Mesa上,GLSL首先被編譯器翻譯成tgsi中間語言,然后顯卡特定的驅動將這些tgsi語言的代碼編譯成GPU指令,這個過程如圖1示(不考慮geometry shader和tesselation shader)。這里有tgsi的詳細描述。這篇碩士論文“高級著色語言及其優化編譯”對GLSL和低級著色語言有比較詳細的論述。
2 R600指令
在CPU上運行的程序,所有的訪存指令和運算指令按照代碼的堆疊順序執行(不考慮指令集并行),如果有跳轉指令則跳轉到相應位置。
GPU主要用于運算,早期的GPU沒有包含復雜的控制程序,R600 Shader程序和CPU程序比較顯得比較簡陋,必須有專門的代碼指示程序的執行順序,并且指示程序運行順序的指令(Control Flow 指令,后面稱為CF 指令)、運算指令(后面稱ALU指令)和訪存指令(在R600 GPU 中稱為Fetch 指令)必須按照類別存放,同一種類型的指令放在一起,不同類型的指令按照某種順序存放,同一類型的指令(不包括CF指令)構成一個Clause。圖2顯示了R600 GPU Shader程序在顯存中存放的形式。
圖2
R600的指令包含Control Flow(后面簡稱CF)指令,ALU(運算)指令,Vertex Fecth (取頂點)指令和Texture Fetch(取紋理)指令。指令的格式稱為Microde Format。
每一個Shader程序(Pixel Shader或者Vertex Shader)包含兩部分,一部分是CF指令,另一部分是Clause。 這些Clause由CF 指令初始化(或者不恰當的理解成Clause 由CF指令調用)。R600的每一條指令的格式(為了保持和手冊上術語的一致,后面將使用Microcode Format這個詞)都包含了2 個或者4個DWORD(CF 和ALU為2個DWORD,Vertex Fetch 和Texture Fetch 為4個DWORD,后面在說地址的時候都是以DWORD為單位的),這些Microcode Format可以在“R600 Family Instruction Set Architecture”手冊上查閱到。
下面將使用兩個實例來說明R600的指令集。和下面這兩個實例等價的GLSL程序大概是這個樣子的:
// vertex shader
attribute vec4 a_position;
attribute vec3 a_texture;
varying vec2 v_texCoord;
void main()
{
gl_Position = a_position;
v_texCoord = a_texture;
}
?// pixel shader
uniform sample2D sampler;
varying vec2 v_texCoord;
void main ()
{
gl_FragColor = texture2D(sampler, v_texCoord);
}
3 Vertex Shader示例
下面使用一個具體的實例來說明,下面的程序來自我們的R600 EXA驅動的Copy過程的Vertex Shader(請參考后續章節),按照圖2的要求,這段程序被分成了兩部分,第一部分是CF指令,共四條指令,指令0~指令3(指令3為空指令,用于對齊),第二部分為取頂點指令,共兩條指令,指令4~ 指令5,分別用于取頂點位置坐標和紋理坐標。
int R600_copy_vs(RADEONChipFamily ChipSet, uint32_t* shader)
{
int i = 0;
/* 0 ? 指令0 */
shader[i++] = CF_DWORD0(ADDR(4));
shader[i++] = CF_DWORD1(POP_COUNT(0),?CF_CONST(0),
COND(SQ_CF_COND_ACTIVE),?I_COUNT(2),?CALL_COUNT(0),
END_OF_PROGRAM(0),?VALID_PIXEL_MODE(0),?CF_INST(SQ_CF_INST_VTX),
WHOLE_QUAD_MODE(0),?BARRIER(1));
/* 1 ?指令1 */
shader[i++] = CF_ALLOC_IMP_EXP_DWORD0(ARRAY_BASE(CF_POS0),?TYPE(SQ_EXPORT_POS),?RW_GPR(1),
RW_REL(ABSOLUTE),?INDEX_GPR(0),?ELEM_SIZE(0));
shader[i++] = CF_ALLOC_IMP_EXP_DWORD1_SWIZ(SRC_SEL_X(SQ_SEL_X),?SRC_SEL_Y(SQ_SEL_Y),?SRC_SEL_Z(SQ_SEL_Z),?
SRC_SEL_W(SQ_SEL_W),?R6xx_ELEM_LOOP(0),?BURST_COUNT(0),?END_OF_PROGRAM(0),
VALID_PIXEL_MODE(0),?CF_INST(SQ_CF_INST_EXPORT_DONE),?WHOLE_QUAD_MODE(0),
BARRIER(1));
/* 2 ?指令2 */
shader[i++] = CF_ALLOC_IMP_EXP_DWORD0(ARRAY_BASE(0),?TYPE(SQ_EXPORT_PARAM),?RW_GPR(0),
RW_REL(ABSOLUTE),?INDEX_GPR(0),?ELEM_SIZE(0));
shader[i++] = CF_ALLOC_IMP_EXP_DWORD1_SWIZ(SRC_SEL_X(SQ_SEL_X),?SRC_SEL_Y(SQ_SEL_Y),
SRC_SEL_Z(SQ_SEL_Z),?SRC_SEL_W(SQ_SEL_W),?R6xx_ELEM_LOOP(0),
BURST_COUNT(0),?END_OF_PROGRAM(1),?VALID_PIXEL_MODE(0),
CF_INST(SQ_CF_INST_EXPORT_DONE),?WHOLE_QUAD_MODE(0),?BARRIER(0));
/* 3 ?指令3*/
shader[i++] = 0x00000000;
shader[i++] = 0x00000000;
/* 4/5 指令4 */
shader[i++] = VTX_DWORD0(VTX_INST(SQ_VTX_INST_FETCH),?FETCH_TYPE(SQ_VTX_FETCH_VERTEX_DATA),
FETCH_WHOLE_QUAD(0),?BUFFER_ID(0),?SRC_GPR(0),?SRC_REL(ABSOLUTE),
SRC_SEL_X(SQ_SEL_X),?MEGA_FETCH_COUNT(16));
shader[i++] = VTX_DWORD1_GPR(DST_GPR(1),?DST_REL(0),?DST_SEL_X(SQ_SEL_X),?DST_SEL_Y(SQ_SEL_Y),
DST_SEL_Z(SQ_SEL_0),?DST_SEL_W(SQ_SEL_1),?USE_CONST_FIELDS(0),
DATA_FORMAT(FMT_32_32_FLOAT),?NUM_FORMAT_ALL(SQ_NUM_FORMAT_SCALED),
FORMAT_COMP_ALL(SQ_FORMAT_COMP_SIGNED),?SRF_MODE_ALL(SRF_MODE_ZERO_CLAMP_MINUS_ONE));
shader[i++] = VTX_DWORD2(OFFSET(0),
#if X_BYTE_ORDER == X_BIG_ENDIAN
ENDIAN_SWAP(SQ_ENDIAN_8IN32),
#else
ENDIAN_SWAP(SQ_ENDIAN_NONE),
#endif
CONST_BUF_NO_STRIDE(0),?MEGA_FETCH(1));
shader[i++] = VTX_DWORD_PAD;
/* 6/7 指令5 */
shader[i++] = VTX_DWORD0(VTX_INST(SQ_VTX_INST_FETCH),?FETCH_TYPE(SQ_VTX_FETCH_VERTEX_DATA),
FETCH_WHOLE_QUAD(0),?BUFFER_ID(0),?SRC_GPR(0),?SRC_REL(ABSOLUTE),
SRC_SEL_X(SQ_SEL_X),?MEGA_FETCH_COUNT(8));
shader[i++] = VTX_DWORD1_GPR(DST_GPR(0),?DST_REL(0),?DST_SEL_X(SQ_SEL_X),?DST_SEL_Y(SQ_SEL_Y),
DST_SEL_Z(SQ_SEL_0),?DST_SEL_W(SQ_SEL_1),?USE_CONST_FIELDS(0),?
DATA_FORMAT(FMT_32_32_FLOAT),?NUM_FORMAT_ALL(SQ_NUM_FORMAT_SCALED),
FORMAT_COMP_ALL(SQ_FORMAT_COMP_SIGNED),
SRF_MODE_ALL(SRF_MODE_ZERO_CLAMP_MINUS_ONE));
shader[i++] = VTX_DWORD2(OFFSET(8),
#if X_BYTE_ORDER == X_BIG_ENDIAN
ENDIAN_SWAP(SQ_ENDIAN_8IN32),
#else
ENDIAN_SWAP(SQ_ENDIAN_NONE),
#endif
CONST_BUF_NO_STRIDE(0),?MEGA_FETCH(0));
shader[i++] = VTX_DWORD_PAD;
return i;
}
上面程序的運行過程如
結合下面這幾張圖詳細描述程序運行的過程。
圖3
圖4
圖5
圖3示,初始狀態,圖中有兩個線程,此刻兩個線程正在要兩個頂點數據進行處理。
- CF指令0,?指令0的ADDR位指示程序從地址4處的指令(指令4)開始運行,I_COUNT位指示共執行2條指令(指令4和指令5),執行完后回到指令0,指令0的END_OF_PROGRAM位表明程序還沒有結束,繼續執行CF指令1。
- Vertex Fetch Clause,CF的指令0指明程序會從第指令4處開始執行,指令4和指令5構成一個Vertex Fetch Clause,兩條指令一起完成取頂點數據,這里是要進行Copy操作,頂點數據包括頂點的位置坐標和紋理坐標。由于是2D操作,因此這里的坐標的有效分量只有兩個。 指令4的VTX_INST位表明改指令是一條取數據的指令,從BUFFER_ID為0 的內存處取頂點(FETCH_TYPE)數據,SRC_GPR為索引號所在的源寄存器位置,一次取的數據量為16字節(一個四元向量的大小),取出來的數據被放置在編號為1的寄存器中(DST_GPR),DST_SEL_X(SQ_SEL_X)表明取出來的向量的X分量放置到目的寄存器第一個DWORD位置處,Y分量放置到第二個DWORD(DST_SEL_Y(SQ_SEL_Y)),目的寄存器的第三個DWORD 處被置為0,第四個DWORD 處被置為1(可以使用0,1或者0.5)。指令5和指令4類似,由于所有頂點屬性數據已經取完,因此原來存在于GPR0 的地址不再需要,可以覆蓋掉。圖4。
- CF指令1和指令2,這是兩條輸出指令,指令所做的工作如圖5示,指令1用于輸出頂點的位置坐標(TYPE(SQ_EXPORT_POS)),這條指令從GPR1(RW_GPR(1))中讀取數據,將數據輸出到Position Buffer 0 中(ARRAY_BASE(CF_POS0))。輸出的時候還有一個Swizzle操作,這條指令的Swizzle操作沒有變換向量各個分量。指令1的END_OF_PROGRAM標志表明程序還沒有結束,因此繼續執行指令2,指令2的END_OF_PROGRAM位表明程序至此結束(后面如果還寫有指令將不會被執行)。
4 Pixel Shader示例
/* copy ps --------------------------------------- */
int R600_copy_ps(RADEONChipFamily ChipSet, uint32_t* shader)
{
int i=0;
/* CF INST 指令 0 */
shader[i++] = CF_DWORD0(ADDR(2));
shader[i++] = CF_DWORD1(POP_COUNT(0),?CF_CONST(0),?COND(SQ_CF_COND_ACTIVE),?I_COUNT(1),
CALL_COUNT(0),?END_OF_PROGRAM(0),?VALID_PIXEL_MODE(0),?CF_INST(SQ_CF_INST_TEX),
WHOLE_QUAD_MODE(0),?BARRIER(1));
/* CF INST 指令 1 */
shader[i++] = CF_ALLOC_IMP_EXP_DWORD0(ARRAY_BASE(CF_PIXEL_MRT0),?TYPE(SQ_EXPORT_PIXEL),?RW_GPR(0),
RW_REL(ABSOLUTE),?INDEX_GPR(0),?ELEM_SIZE(1));
shader[i++] = CF_ALLOC_IMP_EXP_DWORD1_SWIZ(SRC_SEL_X(SQ_SEL_X),?SRC_SEL_Y(SQ_SEL_Y),?
SRC_SEL_Z(SQ_SEL_Z),?SRC_SEL_W(SQ_SEL_W),?R6xx_ELEM_LOOP(0),?BURST_COUNT(1),
END_OF_PROGRAM(1),?VALID_PIXEL_MODE(0),?CF_INST(SQ_CF_INST_EXPORT_DONE),
WHOLE_QUAD_MODE(0),?BARRIER(1));
/* TEX INST 指令 2 */
shader[i++] = TEX_DWORD0(TEX_INST(SQ_TEX_INST_SAMPLE),?BC_FRAC_MODE(0),?FETCH_WHOLE_QUAD(0),
RESOURCE_ID(0),?SRC_GPR(0),?SRC_REL(ABSOLUTE),?R7xx_ALT_CONST(0));
shader[i++] = TEX_DWORD1(DST_GPR(0),?DST_REL(ABSOLUTE),?DST_SEL_X(SQ_SEL_X), /* R */?
DST_SEL_Y(SQ_SEL_Y), /* G */
DST_SEL_Z(SQ_SEL_Z), /* B */
DST_SEL_W(SQ_SEL_W), /* A */
LOD_BIAS(0),
COORD_TYPE_X(TEX_UNNORMALIZED),?COORD_TYPE_Y(TEX_UNNORMALIZED),
COORD_TYPE_Z(TEX_UNNORMALIZED),?COORD_TYPE_W(TEX_UNNORMALIZED));
shader[i++] = TEX_DWORD2(OFFSET_X(0),?OFFSET_Y(0),?OFFSET_Z(0),?SAMPLER_ID(0),?SRC_SEL_X(SQ_SEL_X),
SRC_SEL_Y(SQ_SEL_Y),?SRC_SEL_Z(SQ_SEL_0),?SRC_SEL_W(SQ_SEL_1));
shader[i++] = TEX_DWORD_PAD;
return i;
}
這里總共三條指令,其中CF指令0和CF指令1是兩條CF指令,TEX指令2是一條取紋理的指令。
指令0表明程序將從addr為2的指令2處開始執行,指令2是一條texture fetch 指令,這條指令根據GPR0中給出的紋理坐標(SRC_GPR(0),根據前面semantic的配置,被插值的紋理坐標存放在GPR0中)從id號為0的紋理資源中取出紋理值,放入到GPR0中(DST_GPR(0x0))。
取紋理操作完成后,執行指令1,指令1是一條輸出指令,將取到的紋理直接放到Render target 0(ARRAY_BASE(CF_PIXEL_MRT0))上去。
R600顯卡的指令遠不止以上這些,讀者在理解上面的內容之后,閱讀R600指令集手冊將不會有太大困難,感興趣的可以深入進去了解更多的指令。
?
轉載于:https://www.cnblogs.com/shoemaker/p/linux_graphics11.html
總結
以上是生活随笔為你收集整理的【原创】Linux环境下的图形系统和AMD R600显卡编程(11)——R600指令集的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 烘干机多少钱一台啊?
- 下一篇: 《BI项目笔记》用Excel2013连接