viewon01's picture

OpenCL : how to pass a complex hierarchical structure to OpenCL

Hi,

I have a raytrace software and would like to implement a part of it with OpenCL.
In order to do this I must pass the entire scene information to OpenCL...

My problem is that it is a complex 'structure/class' hierarchy that use a lot of pointers.

And I don't know an efficient way to pass this scene information to OpenCL.

By example :

struct Scene
{
   Lights * lights;
   InstanceList * instanceList;
}
 
struct InstanceList
{
    Instance * instance;
    int Count;
}
 
struct Instance
{
    Geometry * geometry;
    Instance * next;
}
 
struct Geometry
{
   int GeometryType; // 0 = sphere, 1 = cylinder, 2 = Triangle mesh
   Sphere * sphere;
   Cylinder * cylinder;
   TriangleMesh * triangleMesh;
}
 
struct TriangleMesh
{
    int[] Indices;
    float[] Vertices;
}

So, with this (simplified) version of the scene, how can I pass this information to openCl and use it ?

Thanks for your help


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
ctk's picture

That [MarshalAs(UnmanagedType.ByValArray)] is meant for fixed size arrays and you are also required to specify the array size as a constant:
See the bottom of http://msdn.microsoft.com/en-us/library/z6cfh6e6%28VS.71%29.aspx

Just for the heck of it, I tried it out and as expected, it did not work.

viewon01's picture

Thanks,

But do you think that it will work with multi-level ?

like this .... it can be complex to marshal !!

struct InstanceList
{
    Instance * instance;
    int Count;
}
 
struct Instance
{
    Geometry * geometry;
    Instance * next;
}
 
struct Geometry
{
   int GeometryType; // 0 = sphere, 1 = cylinder, 2 = Triangle mesh
   Sphere * sphere;
   Cylinder * cylinder;
   TriangleMesh * triangleMesh;
}
ctk's picture
the Fiddler wrote:

According to msdn:

msdn wrote:

The following complex types are also blittable types:
* One-dimensional arrays of blittable types, such as an array of integers. However, a type that contains a variable array of blittable types is not itself blittable.
* Formatted value types that contain only blittable types if they are marshaled as formatted types.

Vector4 falls into the second category, which means it is blittable. Structs composed of Vector*, Matrix* and Quaternion* fields are also blittable.

I'm afraid I cannot provide example code at this time, as I am not familiar with OpenCL.

Ok, my memory was a little fuzzy. I remember being able to pass vectors inside of a struct (with memory alignment).

The real problem I had was trying to pass an array of structs into the kernel. For example, using the examples above, if I wanted to declare:

TriangleMesh[] lotsOfTriangleMeshes = new TriangleMesh[100];

I could not do it. Marshal.SizeOf() says that it can calculate no meaningful size and I just tried some sample code with OpenTK.BlittableValueType and it says that the arrays of structs is not blittable. Do you have any hints for passing an array of structs into the kernel?

ctk's picture
viewon01 wrote:

Thanks,

But do you think that it will work with multi-level ?

like this .... it can be complex to marshal !!

struct InstanceList
{
    Instance * instance;
    int Count;
}
 
struct Instance
{
    Geometry * geometry;
    Instance * next;
}
 
struct Geometry
{
   int GeometryType; // 0 = sphere, 1 = cylinder, 2 = Triangle mesh
   Sphere * sphere;
   Cylinder * cylinder;
   TriangleMesh * triangleMesh;
}

I'm going to rate this as hard to impossible in your current form. You will have to face both problems of pinning the pointers for all the structs and sub structs (and freeing them) and you will also have to memory align each of the sub structs. The memory alignment value of 16 is only for Vector4 types, which is a fixed type. Your triangleMesh, and I presume cylinder and sphere, are run time determined size. However, I don't believe that you can memory align with dynamic values at run time. Normally for interop, you have fixed size arrays and you could do something like

    [StructLayout(LayoutKind.Explicit)]
    public struct testStruct
    {
        [FieldOffset(0)]
        public structA a;
        [FieldOffset(1)]
        public structB b; 
        [FieldOffset(5)]
        public int c;
        [FieldOffset(9)]
        public byte buffer;
        [FieldOffset(16)]
        public Vector4 d; 
    }

With the dynamically sized arrays in your sub structs, I don't think this is possible to do. I recommend you linearize all your data into one struct.

the Fiddler's picture

If the individual TriangleMesh structs are blittable, you can simply pin the array and pass a pointer. This is how all low-level bindings work in OpenTK. For example, GL.BufferData:

static void BufferData<T>(..., T[] data) where T : struct
{
    GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
    try { BufferData(..., handle.AddrOfPinnedObject()); }
    finally { handle.Free(); }
}
 
static extern void BufferData(..., IntPtr data); // DllImport

If you have a concrete rather a generic type, you can get better performance using unsafe code:

TriangleMesh[] data = new TriangleMesh[100];
unsafe
{
    fixed (TriangleMesh* data_ptr = data)
    {
        BufferData(..., new IntPtr((void*)data_ptr);
    }
}

I'm using GL.BufferData here, but the same code is valid for any other low-level binding in OpenTK.

the Fiddler's picture
ctk wrote:

The memory alignment value of 16 is only for Vector4 types, which is a fixed type. Your triangleMesh, and I presume cylinder and sphere, are run time determined size.

That's true, but you can still request 16byte alignment:

[StructLayout(LayoutKind.Sequential, Pack=16)]
struct InstanceList
{
    Instance * instance;
    int Count;
}
 
[StructLayout(LayoutKind.Sequential, Pack=16)] 
struct Instance
{
    Geometry * geometry;
    Instance * next;
}
 
[StructLayout(LayoutKind.Sequential, Pack=16)]
struct Geometry
{
   int GeometryType; // 0 = sphere, 1 = cylinder, 2 = Triangle mesh
   Sphere * sphere;
   Cylinder * cylinder;
   TriangleMesh * triangleMesh;
}

I don't know if I am missing something obvious, but wouldn't this work?

ctk's picture

The Pack=16 wouldn't necessarily work for the Sphere, Cylinder, and TriangleMesh because they may require different memory alignments. I've just tried a sample with:

    [StructLayout(LayoutKind.Sequential, Pack = 16)]
    unsafe struct TestStruct
    {      
        public float* testVal1;     // array
        public float testVal2;
        public Vector4 vect4;
        public SubStruct subStruct;
    };
 
    [StructLayout(LayoutKind.Sequential)]
    unsafe struct SubStruct
    {
        public float subVal;
    };

The subStruct would give erroneous values in the current layout because it has a different memory alignment requirement compared to the Vector4. If instead I tried

   [StructLayout(LayoutKind.Sequential, Pack = 16)]
    unsafe struct TestStruct
    {      
        public SubStruct subStruct;
        public float* testVal1;     // array
        public float testVal2;
        public Vector4 vect4;
    };
 
    [StructLayout(LayoutKind.Sequential)]
    unsafe struct SubStruct
    {
        public float subVal;
    };

Then, my sample would run correctly.

nythrix's picture

In general lines, passing complex hierarchical data structures to OpenCL is not possible. You'd have to work around this issue the same way as you do with OpenGL, using buffers and whatnots.
I know, OpenCL is much more flexible. But unfortunately (or luckily, depends where you're standing), it still remains a low level abstraction. It doesn't give you any engine-like functionality so you can't process a scene-as-it-is with it.

duke's picture

I'm having a similar problem where my custom struct (a "Transform" holding 3 float4's). I'm curious as to why I can't simply cast it as "(Transform)((float4)(0, 1, 2, 3), (float4)(0, 1, 2, 3), (float4)(0, 1, 2, 3));"

nythrix's picture

That shouldn't be a problem IMO. Pointers are tricky but simple value types should work. How does that struct look like exactly?