初試PyOpenGL三 (Python+OpenGL)GPGPU基本運算與乒乓技術


  這篇GPGPU 概念1: 數組= 紋理 - 文檔文章提出的數組與紋理相等讓人打開新的眼界與思維,本文在這文基礎上,嘗試把這部分思想拿來用在VBO粒子系統上.

  在前面的文章中,我們把CPU的數據傳到GPU后,然后就直接從楨緩沖到顯示屏幕上了,那么還能不能把從GPU的數據拿回來放入CPU,然后進行處理。例如最基本的GPGPU編程中,把數組放入GPU運算后返回CPU。以及圖片用GPU來加速處理。

  和以前把數據從CPU通過相關GLSL,Cg着色器語言傳入數據到GPU並進行處理不同,多了個返回數據到CPU中,因為GPU擅長的是並行處理,所以我們一般把一系列數據處理從CPU中交給GPU。在前面的頂點拾取中,獲取地形高度中,我們都把一系列數據當做紋理,然后從CPU傳入GPU中處理。現在關鍵問題是,如果把GPU數據送回到CPU中。

  在前面這篇文章中(WebGL 利用FBO完成立方體貼圖),我們看到前面,可以先在FBO里,把當前的內存數據經過GPU處理后輸出到應用程序楨緩沖關聯的紋理中,然后此紋理拿來做后面球所需要的立方體貼圖。在這過程中,我們可以知道通過FBO實現,數據從CPU處理傳入GPU,在着色器中進行處理后,然后我們可以在CPU中得到對應FBO中紋理里的數據。

  下面我們完成一個簡單實例,通過把一個數組,傳入GPU中,在GPU中進行處理,然后返回到CPU中並顯示出來。在這我們只介紹,數據一對一的處理,就是傳入多少數據,返回多少數據。

  結合上面所說,大致過程如下,第一步創建FBO,並關聯一個紋理(存放經過GPU處理的數據),然后再創建一個紋理,里面用來存放我們需要處理的數據,因此要求,這個紋理和FBO中關聯的紋理大小一樣,方便處理。第二步,我們在創建的FBO中,來完成一個輸出渲染,因為上步,要求相同大小的紋理,所以這個渲染窗口我們需要特殊處理下,在這窗口,最大保證窗口大小與紋理大小一樣,紋理剛好占用整個窗口大小。這樣才能保證輸出到FBO中的紋理和傳入的紋理索引一一對應。這樣FBO中的紋理就存放的是原始數據經過GPU處理后的數據。

  讓我們來完成整個過程,首先,我們把轉入的數據整理成我們需要的數組列表。請看代碼如下:

 1     def __init__(this,*args):
 2         this.imageFormat = GL_FLOAT
 3         if len(args) == 1 :
 4             if isinstance(args[0],Image):
 5                 image = args[0]
 6                 this.width = image.size[0]
 7                 this.height = image.size[1]
 8                 this.data = image.im
 9                 this.imageFormat = GL_UNSIGNED_BYTE
10             if isinstance(args[0],list):
11                 listf = args[0]
12                 this.width = len(listf) if len(listf) > 0 else 1
13                 this.height = 1
14                 this.data = listf        
15         if len(args) == 3:
16             this.width = int(args[0])
17             this.height = int(args[1])
18             this.data = args[2]  
19         this.imagedata = []
20         for i in range(this.height):
21             for j in range(this.width):
22                 index = i * this.width + j
23                 f4 = this.data[index] if index < len(this.data) else float(j) / float(this.width)
24                 r,g,b,a = 0.0,0.0,0.0,0.0
25                 if isinstance(f4,tuple):
26                     if len(f4) == 2:
27                         r,g = f4
28                     elif len(f4) == 3:
29                         r,g,b = f4
30                     else:
31                         r,g,b,a = f4                           
32                 else:
33                     r = f4
34                 this.imagedata.append(r)                          
35                 this.imagedata.append(g)
36                 this.imagedata.append(b)
37                 this.imagedata.append(a)
38         #this.imagedata = ny.array(this.imagedata,dtype=ny.float)
39         #print this.imagedata
初始化

  這段代碼里,會列舉幾種情況,一種是直接傳入的Image信息,還有是傳入的自定義數組,還有定義了格式化長與寬的數組。然后結合傳入數組的信息,可以是單個數據,也可以是如Image里保存的如r,g,b,a這樣的四元組,整理后,得到我們需要的紋理的長與寬,紋理所保存的數據。

  然后如上面的第一步中所說,創建一個FBO和與之關聯的紋理,還有一個保存需要處理的數據的紋理。具體代碼如下:

 1     def createFBO(this):
 2         this.fbo = glGenFramebuffers(1) 
 3         glBindFramebuffer(GL_FRAMEBUFFER,this.fbo)
 4         #create texture for fbotext,GPU->CPU
 5         this.fbotext = glGenTextures(1)
 6         glBindTexture(GL_TEXTURE_2D,this.fbotext)
 7         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, this.width, this.height,0,GL_RGBA,GL_FLOAT,None)#GL_FLOAT
 8         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
 9         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
10         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
11         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
12         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
13         #relation fbo and fbotext(FBO and Texture2D)
14         glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,this.fbotext,0)                                                                                                     #glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,this.fbotext,0)
15         glBindFramebuffer(GL_FRAMEBUFFER,0)    
16         #save data to texture.CPU->GPU
17         this.fbodata = glGenTextures(1)
18         glBindTexture(GL_TEXTURE_2D,this.fbodata)
19         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
20         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
21         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
22         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
23         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
24         print this.imageFormat
25         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, this.width, this.height, 0, GL_RGBA,this.imageFormat, this.imagedata)
26  
創建FBO與紋理

  就如上面所說,創建FBO,二個紋理,其中紋理需要注意的是glTexImage2D中的第三個參數,這里我們選擇的是GL_RGBA32F,這樣可以在紋理中,對應的r,g,b,a通道保證存放完整的32位浮點數據,而不必要限制在0.0-1.0。下面這些參數按理說可以不設置,但是如果你傳入的是圖片,為了保證處理后的圖片顯示正常,還是有必要設置的。其中glTexImage2D的參數對應詳細說明,如果大家有興趣,可以自己搜索。

  下面就如上面第二步所說,創建輸出渲染,窗口需要與紋理大小一一對應,主要代碼如下:

 1     def renderFBO(this,shader):
 2         glDisable(GL_DEPTH_TEST)
 3         glBindFramebuffer(GL_FRAMEBUFFER,this.fbo)
 4         glPushAttrib(GL_VIEWPORT_BIT)#| GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
 5         glDrawBuffer(GL_COLOR_ATTACHMENT0)
 6         glMatrixMode(GL_PROJECTION)
 7         glLoadIdentity()  
 8         gluOrtho2D(0.0,this.width,0.0,this.height)
 9         glMatrixMode(GL_MODELVIEW)
10         glLoadIdentity() 
11         glViewport(0,0,this.width,this.height)
12         glActiveTexture(GL_TEXTURE0)
13         glBindTexture(GL_TEXTURE_2D, this.fbodata)
14         glUseProgram(shader)
15         glUniform1i(shader.tex0, 0) 
16         glUniform1f(shader.xl, this.width) 
17         glUniform1f(shader.yl, this.height)
18         glBegin(GL_QUADS)
19         glVertex2f(0.0, 0.0)
20         glVertex2f(this.width, 0.0)
21         glVertex2f(this.width, this.height)
22         glVertex2f(0.0, this.height)
23         glEnd()
24         glUseProgram(0) 
25         glBindFramebuffer(GL_FRAMEBUFFER,0)
輸出數據並保存到FBO對應紋理上

  嗯,我發現需要把對應的着色器代碼一起貼出來,然后再好說明。請看對應的着色器代碼如下: 

 1 gpgpu_v = """
 2         //#version 330 compatibility
 3         #version 130
 4         out vec4 pos;
 5         void main() {  
 6             pos = vec4(gl_Vertex);
 7             //The following coding must be in fragment shader
 8             //vec2 xy = v.xy;
 9             //vec2 uv = vec2(xy/vec2(xw,yw)).xy;
10             //o_color = texture2D(tex0, uv);
11             gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
12         }"""
13 
14 gpgpu_f = """
15         //#version 330 compatibility
16         #version 130
17         in vec4 pos;
18         uniform sampler2D tex0; 
19         uniform float xw;
20         uniform float yw;          
21         void main() {             
22             vec2 xy = pos.xy;
23             vec2 uv = vec2(xy/vec2(xw,yw)).xy;            
24             vec4 o_color = texture2D(tex0, uv);// vec4(uv.x,uv.y, 0, 1 );//   
25             o_color = o_color + vec4(0.2);  
26             gl_FragColor = o_color;
27         }"""
着色器

  這二段代碼就是主要的過程了,首先告訴OpenGL,我們用的是應用程序楨緩沖(glBindFramebuffer),是不輸出到屏幕中的。首先就是把輸出窗口和紋理大小一一對應,保證紋理剛好顯示在一個滿屏幕上,因此,在這我們用的投影矩陣是正投影矩陣,和透視投影矩陣的區別,這里就不詳細說明,簡單來說,透視投影會帶給人如人眼中看到的遠處的特殊顯示比較少的效果,而正投影就是遠處和近處大小顯示不改變,他們主要就是對三維點xyz中的z值采用不同的處理造成的。而視圖矩陣,和模型矩陣一樣,他們不需要改變點的位置,直接采用單元矩陣就可。結合着色器中相應代碼,簡單來說,傳入的長與寬,是用了得到相應頂點的索引值,這里相應代碼在上文中的獲取地形的高度一樣的道理,這里不需要x,y二分量分別加0.5,是因為頂點一個是0-x,一個是(-x/2)-x/2.根據我們平常根據索引取數組里的值一樣,只是多考慮成二維的情況。特意說下,相關過程據需要放入片斷着色器中,因為我們是頂點和像素一一對應,只有在片斷中,才是針對像素的處理。

  下面我們來驗證我們二個處理,看下能不能滿足我們。一個是圖片,我們把圖片里的r,g,b,a分量分別加上0.2,這樣看起來應該顯示更亮一些。第二個我們是傳入一個數組,然后按上面我們進行處理后,輸出到控制台里面。

  第一個,傳入數據與相應改動如下(在上文與前面的基礎上): 在gpgpubasic中,新增一個方法,主要是輸出原始圖片,與經過GPU在各分量上加上0.2處理后的圖像。代碼增加如下:

 1     def render(this,shader):
 2         glMatrixMode(GL_PROJECTION)
 3         glLoadIdentity()  
 4         gluOrtho2D(0.0,this.width * 2,0.0,this.height * 2)
 5         glMatrixMode(GL_MODELVIEW)
 6         glLoadIdentity() 
 7         glViewport(0,0,this.width,this.height)
 8 
 9         glColor4f(1.0, 1.0, 1.0, 0.5)
10         glActiveTexture(GL_TEXTURE0)
11         glEnable(GL_TEXTURE_2D)
12         glBindTexture(GL_TEXTURE_2D, this.fbodata)
13         glBegin(GL_QUADS)
14         glTexCoord2f(0.0, 0.0)
15         glVertex2f(0.0, 0.0)
16         glTexCoord2f(1.0, 0.0)
17         glVertex2f(this.width, 0.0)
18         glTexCoord2f(1.0, 1.0)
19         glVertex2f(this.width, this.height)
20         glTexCoord2f(0.0, 1.0)
21         glVertex2f(0.0, this.height)
22         glEnd()
23 
24         glTranslatef(this.width + 10, 0.0, 0.0)        
25         glColor4f(1.0, 1.0, 1.0, 0.5)
26         glActiveTexture(GL_TEXTURE0)
27         glEnable(GL_TEXTURE_2D)
28         glBindTexture(GL_TEXTURE_2D, this.fbotext)
29         glBegin(GL_QUADS)
30         glTexCoord2f(0.0, 0.0)
31         glVertex2f(0.0, 0.0)
32         glTexCoord2f(1.0, 0.0)
33         glVertex2f(this.width, 0.0)
34         glTexCoord2f(1.0, 1.0)
35         glVertex2f(this.width, this.height)
36         glTexCoord2f(0.0, 1.0)
37         glVertex2f(0.0, this.height)
38         glEnd() 
輸出二張圖。

  效果如下:

  顯示效果如我們所想,整體上變亮了。

  第二個例子,我們傳入一個數組,然后在GPU對每個數組加1。相應代碼個性如下:

 1 part = particle.gpgpubasic([11,12,13,14,15])
 2 def InitGL(width,height):
 3     glClearColor(0.1,0.1,0.5,0.1)
 4     glClearDepth(1.0)
 5     glEnable(GL_DEPTH_TEST)
 6     glShadeModel(GL_SMOOTH)
 7     #glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
 8     camera.move(0.0,1.3,0.0)   
 9     camera.setthree(True)
10     camera.length = 3
11     global shaderall
12     shaderall = shaderProg.allshader() 
13     global colorMap 
14     global hightMap      
15     colorMap = loadtexture.Texture.loadmap("ground2.bmp")
16     #hight map for cpu to gpu   
17     hightMap = loadtexture.Texture.loadmap("hight.gif")
18     #create terrain use cpu
19     hightimage = loadtexture.Texture.loadterrain("hight.gif")
20     image = open("ground2.bmp").convert("RGBA")
21     plane.setHeight(hightimage)
22     #global part
23     #part = particle.gpgpubasic(image)
24     part.createFBO()
25     pingpong.createFBO()
26     part.renderFBO(shaderall.gpgpuProgram)
傳入數組。

  因為是混合在上文的代碼中的,所以有一些多余代碼,主要就是創建一個[112,128,132,141,152]的數組,然后把上文中相關傳入圖片的相關代碼隱藏掉。原來着色器中增加vec4(0.2)改成vec4(1.0),並且在上文中的renderFBO添加如下代碼:  

1         glBindFramebuffer(GL_FRAMEBUFFER,this.fbo)
2         glReadBuffer(GL_COLOR_ATTACHMENT0)
3         data = glReadPixels(0, 0, this.width, this.height,GL_RGBA,GL_FLOAT)  
4         print "fbo data:",type(data),len(data),data[0],data[1]#,data[2]
5         glPopAttrib()
6         glBindFramebuffer(GL_FRAMEBUFFER,0)   
7         #close fbo
8         glBindTexture(GL_TEXTURE_2D, 0)        
9         glEnable(GL_DEPTH_TEST)   
輸出經過GPU更改后的數據。

  顯示效果:

  根據前面的代碼,可以看到,返回的數據長度還是5,前二個分量由112,128變成113,129了。

  有了上面這些,我們已經知道如何把CPU的數據經過GPU處理后返回了,下面介紹一個在這基礎之上的乒乓技術,是一個用來把渲染輸出轉換成為下一次運算的輸入的技術,常見的如粒子系統里的運算如果放在GPU中,那么必需用乒乓技術來實現,因為GPU中數據是前次更新加上現在的結果。當然CPU中運算就簡單了,直接每楨改變內存中的數據,然后更新VBO,VAO啥的就行了。

  實現原理很簡單,用FBO關聯二個紋理,在第一渲染時,用第一個紋理數據經GPU處理后放入FBO關聯的第二個紋理中,然后下一楨,把第二個紋理中的數據經過GPU處理放入第一個紋理。代碼如下:

  1 class gpgpupingpong(object):
  2     def __init__(this,*args):
  3         this.imageFormat = GL_FLOAT
  4         this.index = 0
  5         if len(args) == 1 :
  6             if isinstance(args[0],Image):
  7                 image = args[0]
  8                 this.width = image.size[0]
  9                 this.height = image.size[1]
 10                 this.data = image.im
 11                 this.imageFormat = GL_UNSIGNED_BYTE
 12             if isinstance(args[0],list):
 13                 listf = args[0]
 14                 this.width = len(listf) if len(listf) > 0 else 1
 15                 this.height = 1
 16                 this.data = listf        
 17         if len(args) == 3:
 18             this.width = int(args[0])
 19             this.height = int(args[1])
 20             this.data = args[2]  
 21         this.imagedata = []
 22         for i in range(this.height):
 23             for j in range(this.width):
 24                 index = i * this.width + j
 25                 f4 = this.data[index] if index < len(this.data) else float(j) / float(this.width)
 26                 r,g,b,a = 0.0,0.0,0.0,0.0
 27                 if isinstance(f4,tuple):
 28                     if len(f4) == 2:
 29                         r,g = f4
 30                     elif len(f4) == 3:
 31                         r,g,b = f4
 32                     else:
 33                         r,g,b,a = f4                           
 34                 else:
 35                     r = f4
 36                 this.imagedata.append(r)                          
 37                 this.imagedata.append(g)
 38                 this.imagedata.append(b)
 39                 this.imagedata.append(a)
 40     def createFBO(this):
 41         this.fbo = glGenFramebuffers(1) 
 42         glBindFramebuffer(GL_FRAMEBUFFER,this.fbo)
 43         #create texture for fbotext,GPU->CPU
 44         this.fbotext = glGenTextures(1)
 45         glBindTexture(GL_TEXTURE_2D,this.fbotext)
 46         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, this.width, this.height,0,GL_RGBA,GL_FLOAT,None)#GL_FLOAT
 47         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
 48         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
 49         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
 50         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
 51         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
 52         #relation fbo and fbotext(FBO and Texture2D)
 53         glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,this.fbotext,0)                                                                                                     #glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,this.fbotext,0)
 54         
 55         #save data to texture.CPU->GPU
 56         this.fbodata = glGenTextures(1)
 57         glBindTexture(GL_TEXTURE_2D,this.fbodata)
 58         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, this.width, this.height, 0, GL_RGBA,GL_FLOAT,this.imagedata)
 59         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
 60         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
 61         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
 62         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
 63         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
 64         glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT1,GL_TEXTURE_2D,this.fbodata,0)  
 65          
 66         glBindFramebuffer(GL_FRAMEBUFFER,0)    
 67         this.pingpong = [[GL_COLOR_ATTACHMENT0,this.fbodata],[GL_COLOR_ATTACHMENT1,this.fbotext]]
 68     #cpu to gpu,and gpu compute result in fbo and fbotext.(cpu -(fbodata)> gpu
 69     #-(fbotext)> cpu)
 70     def renderFBO(this,shader):        
 71         ind = this.index % 2
 72         this.index = this.index + 1
 73         print "index",ind
 74         pp = this.pingpong[ind]
 75         glDisable(GL_DEPTH_TEST)
 76         glBindFramebuffer(GL_FRAMEBUFFER,this.fbo)
 77         glDrawBuffer(pp[0])
 78         glPushAttrib(GL_VIEWPORT_BIT)
 79         glMatrixMode(GL_PROJECTION)
 80         glLoadIdentity()  
 81         gluOrtho2D(0.0,this.width,0.0,this.height)
 82         glMatrixMode(GL_MODELVIEW)
 83         glLoadIdentity() 
 84         glViewport(0,0,this.width,this.height)
 85         glActiveTexture(GL_TEXTURE0)
 86         glBindTexture(GL_TEXTURE_2D, pp[1])
 87         glUseProgram(shader)
 88         glUniform1i(shader.tex0, 0) 
 89         glUniform1f(shader.xl, this.width) 
 90         glUniform1f(shader.yl, this.height)
 91         glBegin(GL_QUADS)
 92         glVertex2f(0.0, 0.0)
 93         glVertex2f(this.width, 0.0)
 94         glVertex2f(this.width, this.height)
 95         glVertex2f(0.0, this.height)
 96         glEnd()
 97         glUseProgram(0) 
 98         glBindFramebuffer(GL_FRAMEBUFFER,0)
 99         if ind == 0 :
100             glBindFramebuffer(GL_FRAMEBUFFER,this.fbo)
101             glReadBuffer(GL_COLOR_ATTACHMENT0_EXT)
102             data = glReadPixels(0, 0, this.width, this.height,GL_RGBA,GL_FLOAT)  
103             print "fbo 0:",len(data),data[0]#,data[1],data[2]
104             glBindFramebuffer(GL_FRAMEBUFFER,0) 
105         else :
106             glBindFramebuffer(GL_FRAMEBUFFER,this.fbo)
107             glReadBuffer(GL_COLOR_ATTACHMENT1_EXT)
108             data = glReadPixels(0, 0, this.width, this.height,GL_RGBA,GL_FLOAT)  
109             print "fbo 1:",len(data),data[0]#,data[1],data[2]
110             glBindFramebuffer(GL_FRAMEBUFFER,0) 
111 
112         glPopAttrib()  
113         #close fbo
114         glBindTexture(GL_TEXTURE_2D, 0)        
115         glEnable(GL_DEPTH_TEST)  
乒乓技術

  整個過程和上面的差不多,但是需要注意的,在glBindFramebuffer后渲染前,需要指定輸出到的FBO對應的綁定點,就是glDrawBuffer(pp[0])。

  我們可以看下效果。傳入pingpong = particle.gpgpupingpong([10,20,30]),然后每楨每頂點在GPU中加一。效果哪下(跑的有點快,只拿到加到200后的數據):

  主要實踐就到這個,因為上文中,頂點的着色器中處理不復雜,傳入的頂點也不多,大家可能認為作用不大。但是大家如果想下,一張1000*1000像素的紋理,簡單來算GPU只要能並行處理100個點,那也比CPU要高100倍。  

  附件:PythonGPU處理.zip

  今天這個特殊日子,祝大家馬年吉祥,萬事如意。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM