Transform seems to break acceleration structure: what's going on?

I’m experiencing a confusing problem in what should have been a simple program (I’m learning OptiX and playing around with the various options to prepare for a bigger project). Somehow, introducing a Transform into my scene structure breaks the acceleration structure, causing my intersection programs never to be called (tested with printf).

My scene is structured as follows:

1 Group, with 2 children, and a standard “Trbvh”, “Bvh” acceleration structure, set as top node.
Child 0: a geometry group that contains 1 sphere geometry instance and 1 triangle mesh geometry instance, also with a “Trbvh”, “Bvh” structure.
Child 1: a geometry group that contains one single triangle mesh geometry instance, also “Trbvh”, “Bvh”.

With this, my ray tracer works as expected. However, if I introduce a Transform (with unity matrix) in between the top node and child 1 OptiX never calls a single intersection function: not that of Child 1, but not those of Child 0 either. This seems to be related to the acceleration structures, as I can make the problem go away by giving my top group a “NoAccel”, NoAccel" structure instead. If I give either of the children a NoAccel structure very strange behavior ensues (calling the intersection function with ever increasing primitive indices way out of range of the number of primitives until the 2 second display drive crash occurs).

My first thought was an issue with my bounding box functions, but there is no difference in how the bounding box funstions are called and what they return between the different scenarios (transform or no transform, and Trbvh or NoAccel).

Since the issue is that my intersection functions are never called, I assume there must be something wrong with the way I’m building the scene, as the intersect functions and hit programs never come into it and context->validate() also doesn’t throw up any errors.

I’ve pasted some relevant code snippets below, as well as an OAC trace. Any help would be much appreciated, and please let me know if there’s something else I need to show for you to know what my problem is.

// receiver (aka child 0)
GeometryGroup receiver = context->createGeometryGroup();
receiver->setChildCount(2);
receiver->setChild(0u, rec_square);
receiver->setChild(1u, rec_sphere);
receiver->setAcceleration(context->createAcceleration("Trbvh", "Bvh"));

// object (aka child 1)
GeometryGroup obj_gr = context->createGeometryGroup();
obj_gr->setChildCount(1);
obj_gr->setChild(0u, object);
obj_gr->setAcceleration(context->createAcceleration("Trbvh", "Bvh"));

// transform
float matrix[16] = {1.f, 0.f, 0.f, 0.f,
    0.f, 1.f, 0.f, 0.f,
    0.f, 0.f, 1.f, 0.f,
    0.f, 0.f, 0.f, 1.f };
Transform obj_tr = context->createTransform();
obj_tr->setChild(obj_gr);
obj_tr->setMatrix(0, matrix, NULL);

// top node
Group scene = context->createGroup();
scene->setChildCount(2u);
scene->setChild(0u, receiver);
scene->setChild(1u, obj_tr);
scene->setAcceleration(context->createAcceleration("Trbvh", "Bvh"));
context["top_object"]->set(scene);

OAC trace (with all paths removed because I’ve been using absolute paths in my code and am paranoid):

6
64
Platform: Windows
CUDA driver version: 7050
OptiX Version:[3.9.0] Build Number:[20240475Branch%] CUDA Version:[7.5] 64-bit 2015-12-09 
Command line: 
Capture time: 2016-02-17 13:06:24
%%
rtContextCreate( 000000000016F9B0 )
  res = 0
  hdl = 00000000003F1750
rtContextSetRayTypeCount( 00000000003F1750, 2 )
  res = 0
rtContextSetEntryPointCount( 00000000003F1750, 1 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, raygen, 000000000016F9D8 )
  file = oac.ptx.000000.potx
  res = 0
  hdl = 00000000064F6080
rtContextSetRayGenerationProgram( 00000000003F1750, 0, 00000000064F6080 )
  res = 0
rtContextQueryVariable( 00000000003F1750, ray_openangle, 000000000016F938 )
  res = 0
rtContextDeclareVariable( 00000000003F1750, ray_openangle, 000000000016F938 )
  res = 0
  hdl = 000000000668A150
rtVariableSet1f( 000000000668A150, 0.0174533 )
  res = 0
rtContextQueryVariable( 00000000003F1750, tx_pos, 000000000016F938 )
  res = 0
rtContextDeclareVariable( 00000000003F1750, tx_pos, 000000000016F938 )
  res = 0
  hdl = 000000000668A280
rtVariableSet3fv( 000000000668A280, 000000000016FAD0 )
  val = 10 0 0
  res = 0
rtContextQueryVariable( 00000000003F1750, tx_normal, 000000000016F938 )
  res = 0
rtContextDeclareVariable( 00000000003F1750, tx_normal, 000000000016F938 )
  res = 0
  hdl = 0000000006678960
rtVariableSet3fv( 0000000006678960, 000000000016FA60 )
  val = -1 0 0
  res = 0
rtBufferCreate( 00000000003F1750, 2, 000000000016F9E8 )
  // BUFFER_OUTPUT
  res = 0
  hdl = 0000000006678B10
rtBufferSetFormat( 0000000006678B10, 285 )
  // RT_FORMAT_USER
  res = 0
rtBufferSetSize3D( 0000000006678B10, 1, 1, 4 )
  res = 0
rtBufferSetElementSize( 0000000006678B10, 28 )
  res = 0
rtContextQueryVariable( 00000000003F1750, output_buffer1, 000000000016F938 )
  res = 0
rtContextDeclareVariable( 00000000003F1750, output_buffer1, 000000000016F938 )
  res = 0
  hdl = 00000000066785E0
rtVariableSetObject( 00000000066785E0, 0000000006678B10 )
  res = 0
rtGeometryCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 000000000668ADD0
rtGeometrySetPrimitiveCount( 000000000668ADD0, 2 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, bounds, 000000000016F768 )
  file = oac.ptx.000001.potx
  res = 0
  hdl = 000000000668AF90
rtGeometrySetBoundingBoxProgram( 000000000668ADD0, 000000000668AF90 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, intersect, 000000000016F768 )
  file = oac.ptx.000001.potx
  res = 0
  hdl = 00000000067A5C60
rtGeometrySetIntersectionProgram( 000000000668ADD0, 00000000067A5C60 )
  res = 0
rtBufferCreate( 00000000003F1750, 1, 000000000016F778 )
  // BUFFER_INPUT
  res = 0
  hdl = 000000000680A8C0
rtBufferSetFormat( 000000000680A8C0, 285 )
  // RT_FORMAT_USER
  res = 0
rtBufferSetSize1D( 000000000680A8C0, 2 )
  res = 0
rtBufferSetElementSize( 000000000680A8C0, 112 )
  res = 0
rtGeometryQueryVariable( 000000000668ADD0, tri, 000000000016F6C8 )
  res = 0
rtGeometryDeclareVariable( 000000000668ADD0, tri, 000000000016F6C8 )
  res = 0
  hdl = 0000000006816710
rtVariableSetObject( 0000000006816710, 000000000680A8C0 )
  res = 0
rtBufferMap( 000000000680A8C0, 000000000016F8E0 )
  res = 0
rtBufferUnmap( 000000000680A8C0 )
  file = oac.buf.000001.potx
  res = 0
rtMaterialCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 0000000006840ED0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, receiver_hit, 000000000016F768 )
  file = oac.ptx.000002.potx
  res = 0
  hdl = 00000000067ACDC0
rtMaterialSetClosestHitProgram( 0000000006840ED0, 1, 00000000067ACDC0 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, ignore_hit, 000000000016F768 )
  file = oac.ptx.000002.potx
  res = 0
  hdl = 0000000006A8E490
rtMaterialSetAnyHitProgram( 0000000006840ED0, 0, 0000000006A8E490 )
  res = 0
rtGeometryInstanceCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 0000000006A8E8F0
rtGeometryInstanceSetGeometry( 0000000006A8E8F0, 000000000668ADD0 )
  res = 0
rtGeometryInstanceSetMaterialCount( 0000000006A8E8F0, 1 )
  res = 0
rtGeometryInstanceSetMaterial( 0000000006A8E8F0, 0, 0000000006840ED0 )
  res = 0
rtGeometryCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 0000000006AA8780
rtGeometrySetPrimitiveCount( 0000000006AA8780, 1 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, bounds, 000000000016F768 )
  file = oac.ptx.000003.potx
  res = 0
  hdl = 0000000006AA8950
rtGeometrySetBoundingBoxProgram( 0000000006AA8780, 0000000006AA8950 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, intersect, 000000000016F768 )
  file = oac.ptx.000003.potx
  res = 0
  hdl = 0000000006B8DDC0
rtGeometrySetIntersectionProgram( 0000000006AA8780, 0000000006B8DDC0 )
  res = 0
rtMaterialCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 000000000687A1C0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, boundsphere_hit, 000000000016F768 )
  file = oac.ptx.000002.potx
  res = 0
  hdl = 000000000687A280
rtMaterialSetClosestHitProgram( 000000000687A1C0, 0, 000000000687A280 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, ignore_hit, 000000000016F768 )
  file = oac.ptx.000002.potx
  res = 0
  hdl = 0000000006B4CAD0
rtMaterialSetAnyHitProgram( 000000000687A1C0, 1, 0000000006B4CAD0 )
  res = 0
rtGeometryInstanceCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 0000000006B79820
rtGeometryInstanceSetGeometry( 0000000006B79820, 0000000006AA8780 )
  res = 0
rtGeometryInstanceSetMaterialCount( 0000000006B79820, 1 )
  res = 0
rtGeometryInstanceSetMaterial( 0000000006B79820, 0, 000000000687A1C0 )
  res = 0
rtGeometryInstanceQueryVariable( 0000000006B79820, spherepars, 000000000016F6C8 )
  res = 0
rtGeometryInstanceDeclareVariable( 0000000006B79820, spherepars, 000000000016F6C8 )
  res = 0
  hdl = 0000000006B799A0
rtVariableSet4fv( 0000000006B799A0, 000000000016F7F0 )
  val = 10 0 0 3
  res = 0
rtGeometryInstanceQueryVariable( 0000000006B79820, receivepars, 000000000016F6C8 )
  res = 0
rtGeometryInstanceDeclareVariable( 0000000006B79820, receivepars, 000000000016F6C8 )
  res = 0
  hdl = 0000000006879A70
rtVariableSet4fv( 0000000006879A70, 000000000016F7F0 )
  val = -1 0 0 0.005
  res = 0
rtGeometryGroupCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 0000000006879C20
rtGeometryGroupSetChildCount( 0000000006879C20, 2 )
  res = 0
rtGeometryGroupSetChild( 0000000006879C20, 0, 0000000006A8E8F0 )
  res = 0
rtGeometryGroupSetChild( 0000000006879C20, 1, 0000000006B79820 )
  res = 0
rtAccelerationCreate( 00000000003F1750, 000000000016F778 )
  res = 0
  hdl = 000000000687B590
rtAccelerationSetBuilder( 000000000687B590, Trbvh )
  res = 0
rtAccelerationSetTraverser( 000000000687B590, Bvh )
  res = 0
rtGeometryGroupSetAcceleration( 0000000006879C20, 000000000687B590 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, ray_done, 000000000016F9D8 )
  file = oac.ptx.000002.potx
  res = 0
  hdl = 00000000075D4440
rtContextSetMissProgram( 00000000003F1750, 0, 00000000075D4440 )
  res = 0
rtGeometryCreate( 00000000003F1750, 000000000016F288 )
  res = 0
  hdl = 00000000075998D0
rtGeometrySetPrimitiveCount( 00000000075998D0, 8 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, bounds, 000000000016F278 )
  file = oac.ptx.000001.potx
  res = 0
  hdl = 00000000075A21B0
rtGeometrySetBoundingBoxProgram( 00000000075998D0, 00000000075A21B0 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, intersect, 000000000016F278 )
  file = oac.ptx.000001.potx
  res = 0
  hdl = 0000000007632960
rtGeometrySetIntersectionProgram( 00000000075998D0, 0000000007632960 )
  res = 0
rtBufferCreate( 00000000003F1750, 1, 000000000016F288 )
  // BUFFER_INPUT
  res = 0
  hdl = 000000000739B830
rtBufferSetFormat( 000000000739B830, 285 )
  // RT_FORMAT_USER
  res = 0
rtBufferSetSize1D( 000000000739B830, 8 )
  res = 0
rtBufferSetElementSize( 000000000739B830, 112 )
  res = 0
rtGeometryQueryVariable( 00000000075998D0, tri, 000000000016F1D8 )
  res = 0
rtGeometryDeclareVariable( 00000000075998D0, tri, 000000000016F1D8 )
  res = 0
  hdl = 00000000075F6A60
rtVariableSetObject( 00000000075F6A60, 000000000739B830 )
  res = 0
rtBufferMap( 000000000739B830, 000000000016F350 )
  res = 0
rtBufferUnmap( 000000000739B830 )
  file = oac.buf.000003.potx
  res = 0
rtMaterialCreate( 00000000003F1750, 000000000016F288 )
  res = 0
  hdl = 0000000007576460
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, material_hit, 000000000016F278 )
  file = oac.ptx.000002.potx
  res = 0
  hdl = 00000000075765A0
rtMaterialSetClosestHitProgram( 0000000007576460, 0, 00000000075765A0 )
  res = 0
rtProgramCreateFromPTXFile( 00000000003F1750, <path>, terminate_on_hit, 000000000016F278 )
  file = oac.ptx.000002.potx
  res = 0
  hdl = 0000000007664180
rtMaterialSetAnyHitProgram( 0000000007576460, 1, 0000000007664180 )
  res = 0
rtGeometryInstanceCreate( 00000000003F1750, 000000000016F288 )
  res = 0
  hdl = 00000000076A1AD0
rtGeometryInstanceSetGeometry( 00000000076A1AD0, 00000000075998D0 )
  res = 0
rtGeometryInstanceSetMaterialCount( 00000000076A1AD0, 1 )
  res = 0
rtGeometryInstanceSetMaterial( 00000000076A1AD0, 0, 0000000007576460 )
  res = 0
rtGeometryGroupCreate( 00000000003F1750, 000000000016F958 )
  res = 0
  hdl = 00000000076A1C50
rtGeometryGroupSetChildCount( 00000000076A1C50, 1 )
  res = 0
rtGeometryGroupSetChild( 00000000076A1C50, 0, 00000000076A1AD0 )
  res = 0
rtAccelerationCreate( 00000000003F1750, 000000000016F958 )
  res = 0
  hdl = 00000000076A3340
rtAccelerationSetBuilder( 00000000076A3340, Trbvh )
  res = 0
rtAccelerationSetTraverser( 00000000076A3340, Bvh )
  res = 0
rtGeometryGroupSetAcceleration( 00000000076A1C50, 00000000076A3340 )
  res = 0
rtTransformCreate( 00000000003F1750, 000000000016F958 )
  res = 0
  hdl = 00000000076672B0
rtTransformSetChild( 00000000076672B0, 00000000076A1C50 )
  res = 0
rtTransformSetMatrix( 00000000076672B0, 0, 000000000016FB40, 0000000000000000 )
  val = 1 -0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
  res = 0
rtGeometryGroupCreate( 00000000003F1750, 000000000016F9E8 )
  res = 0
  hdl = 0000000007776110
rtGeometryGroupSetChildCount( 0000000007776110, 1 )
  res = 0
rtGeometryGroupSetChild( 0000000007776110, 0, 00000000076A1AD0 )
  res = 0
rtAccelerationCreate( 00000000003F1750, 000000000016F9E8 )
  res = 0
  hdl = 000000000779B3A0
rtAccelerationSetBuilder( 000000000779B3A0, Trbvh )
  res = 0
rtAccelerationSetTraverser( 000000000779B3A0, Bvh )
  res = 0
rtGeometryGroupSetAcceleration( 0000000007776110, 000000000779B3A0 )
  res = 0
rtGroupCreate( 00000000003F1750, 000000000016F9E8 )
  res = 0
  hdl = 00000000076F0380
rtGroupSetChildCount( 00000000076F0380, 2 )
  res = 0
rtGroupSetChild( 00000000076F0380, 0, 0000000006879C20 )
  res = 0
rtGroupSetChild( 00000000076F0380, 1, 00000000076672B0 )
  res = 0
rtAccelerationCreate( 00000000003F1750, 000000000016F9E8 )
  res = 0
  hdl = 00000000076F12C0
rtAccelerationSetBuilder( 00000000076F12C0, Trbvh )
  res = 0
rtAccelerationSetTraverser( 00000000076F12C0, Bvh )
  res = 0
rtGroupSetAcceleration( 00000000076F0380, 00000000076F12C0 )
  res = 0
rtContextQueryVariable( 00000000003F1750, top_object, 000000000016F938 )
  res = 0
rtContextDeclareVariable( 00000000003F1750, top_object, 000000000016F938 )
  res = 0
  hdl = 00000000077C5DE0
rtVariableSetObject( 00000000077C5DE0, 00000000076F0380 )
  res = 0
rtContextValidate( 00000000003F1750 )
  res = 0
rtContextCompile( 00000000003F1750 )
  res = 0
rtContextLaunch2D( 00000000003F1750, 0, 1, 1 )
  file::CUDAbuf::0000000000000000 = oac.CUDAbuf.000003.potx
  file::CUDAbuf::0000000000000000 = oac.CUDAbuf.000004.potx
  file::CUDAbuf::0000000000000000 = oac.CUDAbuf.000005.potx
  file::CUDAbuf::0000000000000000 = oac.CUDAbuf.000006.potx
  res = 0
rtBufferMap( 0000000006678B10, 000000000016FAD0 )
  res = 0

If you have any problems with acceleration structures try the Bvh builder first. That’s the most robust.

I would not recommend to use the Trbvh builder for the root node. There is no geometry under that. Instead use the Bvh builder for the root node which also supports refitting in case you plan to do affine animations.

I would also not recommend to use different primitive types under the same GeometryInstance.
It should work, it’s just my personal preference of structuring things.

You should keep track of all all OptiX objects you create.
Note that the C++ wrappers do not automatically tear down the object hierarchy! You need to explicitly destroy() things just like with the underlying C-API.
Storing the acceleration structure objects comes in handy when in case you update geometry or transforms and need to mark the AS dirty.

As you said the code in the trace is not matching your posted code excerpt. Most obvious difference is that you’re attaching the GeometryInstance created in line 263 to two GeometryGroups in line 277 and 301.
You could actually share that GeometryGroup and its unique acceleration structure in that case. See figure 4 in the OptiX programming Guide.

Other than that, I haven’t spotted a problem by glossing over the trace.

Thanks for the time taken to reply and look through my code and trace in detail.

  1. I forgot to mention I have also tried replacing Trbvh with both Bvh and Sbvh, which all gave the same result. But I’ll make sure not to use Trbvh for my top node in the future.

  2. I’m not sure whether I’ve been unclear, or whether you meant to say I shouldn’t use different primitive types in the same geometry group (rather than instance). I haven’t used different types in the same geometry instance: Child 0 (my receiver) is a geometry group containing two children: rec_square, a geometry instance of a triangle mesh geometry; and rec_sphere, a geometry instance of a sphere geometry.

  3. Thanks for the tip. So far I’ve only really thought about static scenes, but I can see I’ll need to keep track of my Acceleration objects for dynamic scenes. Also good to know about having to destroy() things myself - I thought I understood that everything runs through the Handle class which would automatically destroy objects when they are no longer being referenced. Or is that prevented from happening because a parent references its child and vice versa? That means a bit of a re-write, as I’ve been using functions to un-clutter my main (for example, a function create_receiver that returns my receiver geometry group), so I guess I’d have to replace those by member functions of classes if I have to keep track of the underlying Geometry, GeometryInstance and Acceleration objects.

  4. Well spotted on the single geometry into two geometry groups. The second geometry group wasn’t being used anymore (it also isn’t passed to anything in the trace), and I’ve now commented it out. Sadly, this doesn’t solve my problem either.

So in conclusion, what I get from your comments is that I currently have no way to properly destroy all my objects or to update a dynamic scene, but I find it difficult to imagine either of those can be causing this problem. I’ll do a restructuring anyway tomorrow, but if you (or anyone else) have any more thoughts on what could be causing this problem it would be much appreciated.

Sorry, I misread the GeometryInstance primitive type description.

On the C++ wrappers, the ref-counting in them is only for its own handles, it’s not actually building a shadow hierarchy of the scene graph. It’s your responsibility to track the objects and tear them down.
The wrappers are all in the single header file optixpp_namespace.h. You can see how it implements destroy() explicitly.
It should be possible for you to forge that into a version which actually tracks the scene hierarchy. That wrapper implementation just wasn’t meant to do that.

The easiest way to analyze this would be to get the complete trace to run it in-house and see what’s going on.

Please always provide the following system information when reporting OptiX issues:
OS version, installed GPU(s), display driver version, OptiX version, CUDA Toolkit version you used to build your PTX. (The latter two are already listed in the OAC trace.)

Sometimes it’s just a matter of using a newer display driver.

I hadn’t updated my drivers for a couple of weeks, and indeed: driver 361.91 (released 3 days ago) solves this problem. Thanks a lot for the help, as well as for the pointers on how to improve my usage of OptiX.

Awesome, that was a lucky guess. ;-)
It would have been impossible to isolate without a reproducer given that it was some CUDA driver issue then.