LikeKT's picture

OpenCLOpenGL Interop using VBO: SimpleGL

Credits:
Douglas Andrade's CMSoft OpenCLOpenGL interop example as a template
nythrix for saving me hours of debuging.

Ref to existing discussion: Converting the N-Body C tutorial
http://www.opentk.com/node/1772

FYI: this is not a complete port but to evaluate how to do VBO openCLopenGL interop using OpenTK and Cloo.NET
There is room for improvement, welcome any feedback.

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
 
using System.Windows.Forms;
 
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
using Cloo;
 
 
namespace CLGLInterop
{
    class SimpleGLCloo : GameWindow
    {
        [DllImport("opengl32.dll")]
        extern static IntPtr wglGetCurrentDC();
 
        #region Kernel code
        string source = @"__kernel void sine_wave(__global float4* pos, unsigned int width, unsigned int height, float time)
        {
            unsigned int x = get_global_id(0);
            unsigned int y = get_global_id(1);
 
            // calculate uv coordinates
            float u = x / (float) width;
            float v = y / (float) height;
            u = u*2.0f - 1.0f;
            v = v*2.0f - 1.0f;
 
            // calculate simple sine wave pattern
            float freq = 4.0f;
            float w = sin(u*freq + time) * cos(v*freq + time) * 0.5f;
 
            // write output vertex
            pos[y*width+x] = (float4)(u, w, v, 1.0f);
        }";
        #endregion
 
 
        private float translationX = 0;
        private float translationY = 0;
        private float translationZ = -4;
        private float rotationX = 40;
        private float rotationY = 30;
 
        private int meshWidth = 256;
        private int meshHeight = 256;
        private float animationState = 0.0f;
        private long[] globalWorkSize;
 
        private bool GL_Interop = false;
        private bool initialized = false;
 
        private ComputeContext cxGPUContext;     
        private ComputeCommandQueue cqCommandQueue;
        private ComputeKernel kernel;
 
        private ComputeBuffer<float> mVbo;
        private ComputeBuffer<float> mVertices;
 
        private float[] VerticeArray;
 
        private List<ComputeMemory> computeMemory;
 
        private int[] vbo= new int[1];
        private IntPtr vboMemSize;
        private long vboSize;
 
        public SimpleGLCloo()
        { }
 
        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            VerticeArray = new float[meshWidth * meshHeight * 4];   
 
            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
 
            GL_Interop = true;
            if (GL_Interop)
            {
                #region Creating OpenGL compatible Context
                IntPtr curDC = wglGetCurrentDC();
 
                OpenTK.Graphics.IGraphicsContextInternal ctx = (OpenTK.Graphics.IGraphicsContextInternal)OpenTK.Graphics.GraphicsContext.CurrentContext;
 
                IntPtr raw_context_handle = ctx.Context.Handle;
 
                ComputeContextProperty p1 = new ComputeContextProperty(ComputeContextPropertyName.CL_GL_CONTEXT_KHR, raw_context_handle);
                ComputeContextProperty p2 = new ComputeContextProperty(ComputeContextPropertyName.CL_WGL_HDC_KHR, curDC);
                ComputeContextProperty p3 = new ComputeContextProperty(ComputeContextPropertyName.Platform, ComputePlatform.Platforms[0].Handle);
                List<ComputeContextProperty> props = new List<ComputeContextProperty>() { p1, p2, p3 };
 
                ComputeContextPropertyList Properties = new ComputeContextPropertyList(props);
                cxGPUContext = new ComputeContext(ComputeDeviceTypes.Gpu, Properties, null, IntPtr.Zero);
                #endregion
            }
            else
            {
                ComputeContextProperty p1 = new ComputeContextProperty(ComputeContextPropertyName.Platform, ComputePlatform.Platforms[0].Handle);
                List<ComputeContextProperty> props = new List<ComputeContextProperty>() { p1 };
                ComputeContextPropertyList Properties = new ComputeContextPropertyList(props);
 
                cxGPUContext = new ComputeContext(ComputeDeviceTypes.Gpu, Properties, null, IntPtr.Zero);
            }
 
            cqCommandQueue = new ComputeCommandQueue(cxGPUContext, cxGPUContext.Devices[0], ComputeCommandQueueFlags.None);
 
             // create the program
            ComputeProgram program = new ComputeProgram(cxGPUContext, source);
            program.Build(cxGPUContext.Devices, "", null, IntPtr.Zero);
 
            // Create the kernel
            kernel = program.CreateKernel("sine_wave");
 
            initVBO();
 
            if (GL_Interop)
            {
                kernel.SetMemoryArgument(0, this.mVbo); 
            }
            else
            {
                kernel.SetMemoryArgument(0, this.mVertices); 
            }
            kernel.SetValueArgument<int>(1, meshWidth );
            kernel.SetValueArgument<int>(2, meshHeight );
 
            cqCommandQueue = new ComputeCommandQueue(cxGPUContext, cxGPUContext.Devices[0], ComputeCommandQueueFlags.None);
            initialized = true;
        }
 
        /**
         * Create the vertex buffer object (VBO) that stores the
         * vertex positions.
         */
        private void initVBO()
        {
            if (vbo[0] != 0)
            {
                GL.DeleteBuffers(1, ref vbo[0]);
                vbo[0] = 0;
            }
            GL.GenBuffers(1, out vbo[0]);
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo[0]);
            vboSize = meshWidth * meshHeight * 4 * sizeof(float);
            vboMemSize = Marshal.AllocHGlobal((int)vboSize);
 
            GL.BufferData(BufferTarget.ArrayBuffer, vboMemSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
 
 
            if (GL_Interop)
            {
                mVbo = ComputeBuffer<float>.CreateFromGLBuffer<float>(cxGPUContext, ComputeMemoryFlags.WriteOnly, vbo[0]);
            }
            else
            {
                mVertices = new ComputeBuffer<float>(cxGPUContext, ComputeMemoryFlags.WriteOnly, VerticeArray);   
            }
 
        }
        protected override void OnUnload(EventArgs e)
        {
            base.OnUnload(e);
 
            GL.DeleteBuffers(1, ref vbo[0]);
        }
 
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
 
            GL.Viewport(0, 0, Width, Height);
            GL.MatrixMode(MatrixMode.Projection);
            float degreeRadian = MathHelper.DegreesToRadians(50.0f);
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(degreeRadian, Width / (float)Height, 0.1f, 100.0f);
            GL.LoadMatrix(ref projection);
        }
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
 
            if (Keyboard[Key.Escape])
                Exit();
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            if (!initialized)
            {
                return;
            }
 
            runKernel();
 
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
            GL.Translate(translationX, translationY, translationZ);
            GL.Rotate(rotationX, 1.0f, 0.0f, 0.0f);
            GL.Rotate(rotationY, 0.0f, 1.0f, 0.0f);
            rotationY += 0.5f;
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo[0]);
            GL.VertexPointer(4, VertexPointerType.Float, 0, 0);
            GL.EnableClientState(ArrayCap.VertexArray);
 
            GL.Color3(1.0f, 0.0f, 0.0f);
            GL.DrawArrays(BeginMode.Points, 00, meshWidth * meshHeight);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.DisableClientState(ArrayCap.VertexArray);
 
            animationState += 0.01f;
 
            SwapBuffers();
        }
 
        private void runKernel()
        {
 
            if (GL_Interop)
            {
                GL.Finish();
                computeMemory = new List<ComputeMemory>() { this.mVbo };
                cqCommandQueue.AcquireGLObjects(computeMemory, null);
            }
 
            kernel.SetValueArgument<float>(3, animationState); 
            globalWorkSize = new long[2];
            globalWorkSize[0] = meshWidth;
            globalWorkSize[1] = meshHeight;
 
            cqCommandQueue.Execute(kernel, null, globalWorkSize, null, null);
 
 
            if (GL_Interop)
            {
                    cqCommandQueue.Finish();
                    cqCommandQueue.ReleaseGLObjects(computeMemory, null); 
            }
            else
            {
                GL.BindBuffer(BufferTarget.ArrayBuffer, vbo[0]);
 
                IntPtr pointer = GL.MapBuffer(BufferTarget.ArrayBuffer, BufferAccess.WriteOnly);
 
                cqCommandQueue.Read<float>(this.mVertices, true, 0,
                        meshWidth * meshHeight * 4, pointer, null);
 
                GL.UnmapBuffer(BufferTarget.ArrayBuffer);
                GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            }
        }
 
        [STAThread]
        static void Main()
        {
            using (SimpleGLCloo game = new SimpleGLCloo())
            {
                game.Run(30.0);
            }
        }
    }
}