Optix Prime can't handle custom ray structures

Hello all,

In optix prime I believe the ray structures are required to be user defined. Is this because they can be customised? For instance, I would like to attach other information to a ray:

using RayBigger_t = struct RayBiggerStruct
{
    float3 origin;
    float  tmin;
    float3 dir;
    float  tmax;

    int blah;
    int dog;
    float apple;
};

But if I do so, optix prime never seems to register any hits for anything except the first ray (hit.t == -1).

Is this expected behaviour? If I can’t customise the structure, then why do I have to always define it myself, and why aren’t the few types available just defined in the optix prime headers?

Thanks!

Below is some code that demonstrates the problem. If I run it with an unmodified ray structure, it works fine.

using namespace optix::prime;

    Context context = Context::create(RTP_CONTEXT_TYPE_CPU);
    Model model = context->createModel();
    float3 v1 {0, 3, 1};
    float3 v2 {-1, 3, -0.5};
    float3 v3 {1, 3, -0.5};
    float3 vlist[3] = {v1, v2, v3};
    int3 t1 {0, 1, 2};

    const int num_tris = 1;
    const int num_vertices = 3;

    model->setTriangles(num_tris, RTP_BUFFER_TYPE_HOST, &t1, num_vertices, RTP_BUFFER_TYPE_HOST, &vlist);
    model->update( 0 );

    // generate a Ray_t at the origin in the y direction
    float3 origin {0, 0, 0};
    float3 direction {0, 1, 0};

    RayBigger_t r[2] {{origin, 0, direction, 1e8, 4, 5, 6}, {origin, 0, direction, 1e8, 4, 5, 6}};
    Hit_t h[2];

    const int num_rays = 2;
    const int num_hits = 2;

    Query query = model->createQuery(RTP_QUERY_TYPE_CLOSEST);
    query->setRays(num_rays, RTP_BUFFER_FORMAT_RAY_ORIGIN_TMIN_DIRECTION_TMAX, RTP_BUFFER_TYPE_HOST, &r[0]);
    query->setHits(num_hits, RTP_BUFFER_FORMAT_HIT_T_TRIID_U_V, RTP_BUFFER_TYPE_HOST, &h[0]);
    query->execute(0);

    REQUIRE(h[0].t == Approx(3.0f));
    REQUIRE(h[0].triId == 0);
    REQUIRE(h[0].u == Approx(1.0f/3));
    REQUIRE(h[0].v == Approx(1.0f/3)); // these are OK!

    REQUIRE(h[1].t == Approx(3.0f));  // not OK here!
    REQUIRE(h[1].triId == 0);
    REQUIRE(h[1].u == Approx(1.0f/3));
    REQUIRE(h[1].v == Approx(1.0f/3));

OptiX Prime doesn’t support custom ray payloads. It only handles intersection testing with the defined buffer formats for queries and hit results.

You send a number of rays, you get the same number of hit results.
If you maintain other buffers with the same number of entries for your user defined per ray data, you can access that data with the exact same cell index you use to retrieve the hit result.

Hi Detlef,

Thanks for your response. Is there any way at all to change this behaviour (even unsupported)? Are there plans to remove this restriction?

I’m trying to do a thrust based multi-criteria in-place sort of the ray/hit combos combined with some other information, and repeatedly synchronising the order of several buffers seems to result in a fairly significant slowdown. Alternatively, storing mapping tables between the pieces of data would use a few hundred megabytes of memory that I’d rather not give up. Not to mention that it makes things much more complicated.

Hi Jeremy,

We don’t plan to add custom data layouts in Prime, which is meant to be fixed function, special purpose. You can do this in OptiX, and you would get good performance.

Check out the new formats that come with instancing support in 3.7. You might be able to store enough ray ID info in this format.

This is exactly what I want to do.

For that I assume I need to use : rtpBufferDescCreate

If my understanding is correct, if I only want to use 2 int, I will still need to use one of RTPbufferformat available:

/*! Buffer formats */
enum RTPbufferformat
{
  /* INDICES */
  RTP_BUFFER_FORMAT_INDICES_INT3                   = 0x400, /*!< Index buffer with 3 integer vertex indices per triangle */
  RTP_BUFFER_FORMAT_INDICES_INT3_MASK_INT          = 0x401, /*!< Index buffer with 3 integer vertex indices per triangle, and an integer visibility mask */

  /* VERTICES */
  RTP_BUFFER_FORMAT_VERTEX_FLOAT3                  = 0x420, /*!< Vertex buffer with 3 floats per vertex position */
  RTP_BUFFER_FORMAT_VERTEX_FLOAT4                  = 0x421, /*!< Vertex buffer with 4 floats per vertex position */

  /* RAYS */
  RTP_BUFFER_FORMAT_RAY_ORIGIN_DIRECTION           = 0x440, /*!< float3:origin float3:direction */
  RTP_BUFFER_FORMAT_RAY_ORIGIN_TMIN_DIRECTION_TMAX = 0x441, /*!< float3:origin, float:tmin, float3:direction, float:tmax */
  RTP_BUFFER_FORMAT_RAY_ORIGIN_MASK_DIRECTION_TMAX = 0x442, /*!< float3:origin, int:mask, float3:direction, float:tmax. If used, buffer format RTP_BUFFER_FORMAT_INDICES_INT3_MASK_INT is required! */

  /* HITS */
  RTP_BUFFER_FORMAT_HIT_BITMASK                    = 0x460, /*!< one bit per ray 0=miss, 1=hit */
  RTP_BUFFER_FORMAT_HIT_T                          = 0x461, /*!< float:ray distance (t < 0 for miss) */
  RTP_BUFFER_FORMAT_HIT_T_TRIID                    = 0x462, /*!< float:ray distance (t < 0 for miss), int:triangle id */
  RTP_BUFFER_FORMAT_HIT_T_TRIID_U_V                = 0x463, /*!< float:ray distance (t < 0 for miss), int:triangle id, float2:barycentric coordinates u,v (w=1-u-v) */

  RTP_BUFFER_FORMAT_HIT_T_TRIID_INSTID             = 0x464, /*!< float:ray distance (t < 0 for miss), int:triangle id, int:instance position in list */
  RTP_BUFFER_FORMAT_HIT_T_TRIID_INSTID_U_V         = 0x465, /*!< float:ray distance (t < 0 for miss), int:triangle id, int:instance position in list, float2:barycentric coordinates u,v (w=1-u-v) */

  /* INSTANCES */
  RTP_BUFFER_FORMAT_INSTANCE_MODEL                 = 0x480, /*!< RTPmodel:objects of type RTPmodel */

  /* TRANSFORM MATRICES */
  RTP_BUFFER_FORMAT_TRANSFORM_FLOAT4x4             = 0x490, /*!< float:row major 4x4 affine matrix (it is assumed that the last row has the entries 0.0f, 0.0f, 0.0f, 1.0f, and will be ignored) */
  RTP_BUFFER_FORMAT_TRANSFORM_FLOAT4x3             = 0x491  /*!< float:row major 4x3 affine matrix */
};

So in my case:

RTP_BUFFER_FORMAT_INDICES_INT3
//
		RTPbufferdesc payloads;
		CBuffer<int2> payloadsBuffer( 0, bufferType, LOCKED );

// fill the buffer .. but not the 3rd int (i don't need it) 

		CHK_PRIME( rtpBufferDescCreate(
				context,
				RTP_BUFFER_FORMAT_INDICES_INT3 ,
				payloadsBuffer.type(),
				payloadsBuffer.ptr(),
				&payloads )
		);

Am I correct ?

Or are those types of Buffer reserved ?

No, there is no need to use any of the OptiX Prime buffer formats or API calls for the data you do not send to OptiX Prime. You as a developer are fully responsible for all code which happens before doing a query and after you got hit results, and so forth. E.g. attribute interpolation, lighting and shading calculations, new ray generation, etc.
Basically the code you’ll find inside the primeKernels.cu files and the shadeHits() routine inside some OptiX Prime SDK examples.

Means however you want to manage your other data, you just need to make sure you can index it matching the query and hit result index.

To handle this on the GPU in parallel you would normally write these things in CUDA and use the resp. CUDA malloc runtime calls to manage that additional data on your own in whatever format you like. Search for “alloc” inside the OptiX Prime examples source code and you’ll also find “cudaMalloc” already.

The shadeHits() routine in the primeCommon.cpp file of the primeSimple example could also be implemented using CUDA. In the current implementation it’s running on the CPU, but you’ll get the idea. Instead of the indices and vertices arrays there, you could manage any additional data you need there on your own.

Of thanks for correcting me. My idea was to be able to use similar part of code for buffer creation when running either on CPU and GPU but I was wrong.

I will write my own structures of size being equal to the size of the raysBuffer