C#渲染OpenCL生成的图像

问题:我正在尝试实时渲染动态Julia分形。 因为分形在不断变化,我需要能够每秒渲染至少20帧,最好是更多。 您需要了解的Julia分形是每个像素都可以独立计算,因此任务很容易并行化。

第一种方法:因为我已经习惯在C#中使用Monogame,所以我尝试在HLSL中编写一个可以完成这项工作的着色器,但是编译器一直在抱怨,因为我用了超过允许的64个算术插槽(我需要至少一千个) )。

第二种方法:使用CPU,可以预期大约两分钟生成一帧。

第三种方法:我开始使用名为Cloo的包装器学习OpenCL的基础知识。 实际上,通过使用OpenCL计算图像数据,然后从GPU获取数据,将数据存储在Texture2D中并将纹理绘制到屏幕上,我获得了快速,好的结果。 对于1000×1000图像,我每秒大约可以获得13帧。 这仍然不是我所希望的,因为图像应该是1920×1080来填满我的屏幕,并且帧速率非常明显。 我意识到我实际上是在GPU上生成图像,将数据发送到CPU然后将其发送回GPU,所以这似乎是一个不必要的步骤,如果可以删除,可能会解决我的问题。 我在一些论坛上看到OpenGL能够做到这一点,但我无法找到具体的信息。

问题:首先,是否有一种简单的方法可以直接绘制OpenCL生成的数据,而不涉及CPU(最好与Monogame兼容)? 如果不是这种情况,是否可以使用OpenGL实现它,然后将其与Monogame结合使用? 其次,为什么用简单的HLSL着色器不可能实现这一点? 由于HLSL和OpenCL都使用GPU,为什么HLSL在进行许多算术运算时会受到如此多的限制?

编辑

我发现这个网站大致完成了我想要的,但是使用了GLSL着色器。 这再次质疑我在HLSL中的成功。 不幸的是,由于monogame不支持GLSL(尚未),我的问题仍然没有答案。

对不起我不使用OpenCL也不使用C#但你可以在使用GLSL的着色器内完全完成这项工作(但是你可能会遇到精度问题,因为Julia就像分形有时甚至是64位double还不够)。 无论如何,这里有一个Mandelbrot集的简单例子,我在几年前做了…

CPU端app C ++ / OpenGL / GLSL / VCL代码::

 //--------------------------------------------------------------------------- #include  #pragma hdrstop #include "Unit1.h" // VCL window header #include "gl\\OpenGL3D_double.cpp" // my GL engine //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; OpenGLscreen scr; GLSLprogram shd; float mx=0.0,my=0.0,mx0=0.0,my0=0.0,mx1=0.0,my1=0.0; TShiftState sh0,sh1; int xs=1,ys=1; int txrmap=-1; float zoom=1.000; unsigned int queryID[2]; //--------------------------------------------------------------------------- void gl_draw() { float x,y,dx,dy; scr.cls(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // matrix for old GL rendering glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMatrixMode(GL_TEXTURE); glLoadIdentity(); // GLSL uniforms shd.bind(); shd.set1i("txrmap",0); // texture unit shd.set2f("p0",mx,my); // pan position shd.set1f("zoom",zoom); // zoom // issue the first query // Records the time only after all previous // commands have been completed glQueryCounter(queryID[0], GL_TIMESTAMP); // QUAD covering screen scr.txrs.bind(txrmap); glColor3f(1.0,1.0,1.0); glBegin(GL_QUADS); glTexCoord2f(0.0,0.0); glVertex2f(-1.0,+1.0); glTexCoord2f(0.0,1.0); glVertex2f(-1.0,-1.0); glTexCoord2f(1.0,1.0); glVertex2f(+1.0,-1.0); glTexCoord2f(1.0,0.0); glVertex2f(+1.0,+1.0); glEnd(); shd.unbind(); scr.txrs.unbind(); // issue the second query // records the time when the sequence of OpenGL // commands has been fully executed glQueryCounter(queryID[1], GL_TIMESTAMP); // GL driver info and GLSL log scr.text_init_pix(1.0); glColor4f(0.0,0.2,1.0,0.8); scr.text(glGetAnsiString(GL_VENDOR)); scr.text(glGetAnsiString(GL_RENDERER)); scr.text("OpenGL ver: "+glGetAnsiString(GL_VERSION)); glColor4f(0.4,0.7,0.8,0.8); for (int i=1;i<=shd.log.Length();) scr.text(str_load_lin(shd.log,i,true)); scr.text_exit(); scr.exe(); scr.rfs(); // wait until the results are available int e; unsigned __int64 t0,t1; for (e=0;!e;) glGetQueryObjectiv(queryID[0],GL_QUERY_RESULT_AVAILABLE,&e); for (e=0;!e;) glGetQueryObjectiv(queryID[1],GL_QUERY_RESULT_AVAILABLE,&e); glGetQueryObjectui64v(queryID[0], GL_QUERY_RESULT, &t0); glGetQueryObjectui64v(queryID[1], GL_QUERY_RESULT, &t1); Form1->Caption=AnsiString().sprintf("Time spent on the GPU: %f ms\n", (t1-t0)/1000000.0); } //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner) { scr.init(this); OpenGLtexture txr; txr.load ("gradient.jpg"); txrmap=scr.txrs.add(txr); shd.set_source_file("","","","Mandelbrot_set.glsl_vert","Mandelbrot_set.glsl_frag"); glGenQueries(2, queryID); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender) { scr.exit(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormResize(TObject *Sender) { scr.resize(); xs=ClientWidth; ys=ClientHeight; gl_draw(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormPaint(TObject *Sender) { gl_draw(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X,int Y) { bool q0,q1; mx1=1.0-divide(X+X,xs-1); my1=divide(Y+Y,ys-1)-1.0; sh1=Shift; q0=sh0.Contains(ssLeft); q1=sh1.Contains(ssLeft); if (q1) { mx-=(mx1-mx0)*zoom; my-=(my1-my0)*zoom; } mx0=mx1; my0=my1; sh0=sh1; gl_draw(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y) { FormMouseMove(Sender,Shift,X,Y); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y) { FormMouseMove(Sender,Shift,X,Y); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseWheelDown(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled) { zoom*=1.2; gl_draw(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseWheelUp(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled) { zoom/=1.2; gl_draw(); } //--------------------------------------------------------------------------- 

你可以忽略大部分代码重要的东西是gl_draw()渲染单个QUAD覆盖整个屏幕并传递zoompan位置。 此代码使用旧样式glBegin/glEnd和默认nVidia位置,因此它可能无法在不同的供应商gfx驱动程序上运行。 网格应该在VAO / VBO中,因此布局位置将匹配以查看如何执行此操作以查看答案末尾的链接或将着色器移植到兼容性配置文件。

顶点:

 // Vertex #version 420 core layout(location=0) in vec2 pos; // glVertex2f <-1,+1> out smooth vec2 p; // texture end point <0,1> void main() { p=pos; gl_Position=vec4(pos,0.0,1.0); } 

分段:

 // Fragment #version 420 core uniform sampler2D txrmap; // texture unit for light map uniform vec2 p0=vec2(0.0,0.0); // mouse position <-1,+1> uniform float zoom=1.000; // zoom [-] in smooth vec2 p; out vec4 col; void main() { int i,n; vec2 pp; float x,y,q,xx,yy; pp=(p*zoom)-p0; // y (-1, 1) pp.x=(1.75*pp.x)-0.75; // x (-2.5, 1) for (x=0.0,y=0.0,xx=0.0,yy=0.0,i=0,n=200;(i 

使用此纹理作为渐变:

梯度

结果截图:

截图

如果您需要开始使用GLSL (以替换我的gl引擎),请参阅:

  • C ++中简单完整的GL + VAO / VBO + GLSL +着色器示例

但我相信在C#中必须有大量的教程,所以google

为了解决这些问题:是的,OpenCL可以画画,但是Monogame显然没有封装在CL的顶部,所以不是问题1.问题2是正确的问题:也许,请参阅下面的建议。 问题3:HLSL本质上是PS 1.1所以“为什么不可能”是因为PS演变为2.x以通过更广泛的数据管道来管理并行化…所以你需要Dx12支持或GLSL / OpenGL。

由于您使用CLoo接近性能预期,为什么不尝试使用OpenCL.Net和/或OpenTK将Julia计算与Monogame API更紧密地绑定在一起? – 如果你必须去GPU-CPU-GPU至少使它成为尽可能宽的管道。

或者,对并行化和帧速率问题稍微侧重的解决方案可能是将GP-GPU包装器(如Quanta的Alea)与Monogame解决方案集成在一起。 我建议看看Cudafy,但Alea更强大,支持跨厂商GPU。

构建过程将决定Julia代码的哪一部分将通过Alea在GPU上计算,并且Monogame部分将接收用于渲染的像素字段。 关键点将是库“玩得很好”的兼容性,最终,如果你让它工作,帧速率。

一句话:你选择在HLSL(阅读:微软Dx9)和Monogame不支持GLSL / Dx12 ….你会被困住,所以你必须创造性地操纵才能解决问题。