Error updating IAS transform member (Optix 7.1)

I have been working on adapting the texture mesh viewer example from the Optix SIGGRAPH tutorial to allow for continuous update of the mesh position. Optix is returning a build error when I attempt to update the acceleration structure after the initial construction. The example originally loads a .obj file using the tinyobjloader, and primitives are built into an acceleration structure followed by compaction:

OptixTraversableHandle SampleRenderer::buildMeshAccels()
{
  const int numMeshes = (int)model->meshes.size();
  vertexBuffer.resize(numMeshes);
  normalBuffer.resize(numMeshes);
  texcoordBuffer.resize(numMeshes);
  indexBuffer.resize(numMeshes);
  
  OptixTraversableHandle asHandle { 0 };

  // ==================================================================
  // triangle inputs
  // ==================================================================
  std::vector<OptixBuildInput> triangleInput(numMeshes);
  std::vector<CUdeviceptr> d_vertices(numMeshes);
  std::vector<CUdeviceptr> d_indices(numMeshes);
  std::vector<uint32_t> triangleInputFlags(numMeshes);

  for (int meshID=0;meshID<numMeshes;meshID++) {
    // upload the model to the device: the builder
    TriangleMesh &mesh = *model->meshes[meshID];
    vertexBuffer[meshID].alloc_and_upload(mesh.vertex);
    indexBuffer[meshID].alloc_and_upload(mesh.index);
    if (!mesh.normal.empty())
      normalBuffer[meshID].alloc_and_upload(mesh.normal);
    if (!mesh.texcoord.empty())
      texcoordBuffer[meshID].alloc_and_upload(mesh.texcoord);

    triangleInput[meshID] = {};
    triangleInput[meshID].type
      = OPTIX_BUILD_INPUT_TYPE_TRIANGLES;

    // create local variables, because we need a *pointer* to the
    // device pointers
    d_vertices[meshID] = vertexBuffer[meshID].d_pointer();
    d_indices[meshID]  = indexBuffer[meshID].d_pointer();
    
    triangleInput[meshID].triangleArray.vertexFormat        = OPTIX_VERTEX_FORMAT_FLOAT3;
    triangleInput[meshID].triangleArray.vertexStrideInBytes = sizeof(vec3f);
    triangleInput[meshID].triangleArray.numVertices         = (int)mesh.vertex.size();
    triangleInput[meshID].triangleArray.vertexBuffers       = &d_vertices[meshID];
  
    triangleInput[meshID].triangleArray.indexFormat         = OPTIX_INDICES_FORMAT_UNSIGNED_INT3;
    triangleInput[meshID].triangleArray.indexStrideInBytes  = sizeof(vec3i);
    triangleInput[meshID].triangleArray.numIndexTriplets    = (int)mesh.index.size();
    triangleInput[meshID].triangleArray.indexBuffer         = d_indices[meshID];
  
    triangleInputFlags[meshID] = 0;
  
    // in this example we have one SBT entry, and no per-primitive
    // materials:
    triangleInput[meshID].triangleArray.flags               = &triangleInputFlags[meshID];
    triangleInput[meshID].triangleArray.numSbtRecords               = 1;
    triangleInput[meshID].triangleArray.sbtIndexOffsetBuffer        = 0; 
    triangleInput[meshID].triangleArray.sbtIndexOffsetSizeInBytes   = 0; 
    triangleInput[meshID].triangleArray.sbtIndexOffsetStrideInBytes = 0;
  }
  // ==================================================================
  // BLAS setup
  // ==================================================================
  
  OptixAccelBuildOptions accelOptions = {};
  accelOptions.buildFlags             = OPTIX_BUILD_FLAG_ALLOW_UPDATE
    | OPTIX_BUILD_FLAG_ALLOW_COMPACTION
    ;
  accelOptions.motionOptions.numKeys  = 0;
  accelOptions.operation              = OPTIX_BUILD_OPERATION_BUILD;

  OptixAccelBufferSizes blasBufferSizes;
  OPTIX_CHECK(optixAccelComputeMemoryUsage
              (optixContext,
               &accelOptions,
               triangleInput.data(),
               (int)numMeshes,  // num_build_inputs
               &blasBufferSizes
               ));


  // ==================================================================
  // prepare compaction
  // ==================================================================
  
  CUDABuffer compactedSizeBuffer;
  compactedSizeBuffer.alloc(sizeof(uint64_t));
  
  OptixAccelEmitDesc emitDesc;
  emitDesc.type   = OPTIX_PROPERTY_TYPE_COMPACTED_SIZE;
  emitDesc.result = compactedSizeBuffer.d_pointer();
  
  // ==================================================================
  // execute build (main stage)
  // ==================================================================

  CUDABuffer tempBuffer;
  tempBuffer.alloc(blasBufferSizes.tempSizeInBytes);
  
  CUDABuffer outputBuffer;
  outputBuffer.alloc(blasBufferSizes.outputSizeInBytes);

  OPTIX_CHECK(optixAccelBuild(optixContext,
                              /* stream */0,
                              &accelOptions,
                              triangleInput.data(),
                              (int)numMeshes,

                              tempBuffer.d_pointer(),
                              tempBuffer.sizeInBytes,
                              
                              outputBuffer.d_pointer(),
                              outputBuffer.sizeInBytes,
                              
                              &asHandle,
                              
                              &emitDesc,
                              1
                              ));
  CUDA_SYNC_CHECK();
  
  // ==================================================================
  // perform compaction
  // ==================================================================
  uint64_t compactedSize;
  compactedSizeBuffer.download(&compactedSize,1);
  
  gasBuffer.alloc(compactedSize);
  
  OPTIX_CHECK(optixAccelCompact(optixContext,
                                /*stream:*/0,
                                gasHandle,
                                gasBuffer.d_pointer(),
                                gasBuffer.sizeInBytes,
                                &gasHandle));
  CUDA_SYNC_CHECK();
  
  // ==================================================================
  // aaaaaand .... clean up
  // ==================================================================
  outputBuffer.free(); // << the UNcompacted, temporary output buffer
  tempBuffer.free();
  compactedSizeBuffer.free();

  return gasHandle;
}

My understanding is that in order to apply a transform to this GAS after it has been built, it needs to be built into a parent IAS which has a transform attribute that can be modified. I built an IAS with the GAS transversable handle assigned to the instance, and the optic program manages to run with no problems:

OptixTraversableHandle SampleRenderer::buildInstanceAccel()
{   
    
    OptixTraversableHandle iasHandle{ 0 };
    
    OptixInstance instance;
    memcpy(instance.transform, launchParams.meshTransform, sizeof(float)*12);
    instance.instanceId = 0;
    instance.visibilityMask = 255;
    instance.sbtOffset = 0;
    instance.flags = OPTIX_INSTANCE_FLAG_NONE;
    instance.traversableHandle = gasHandle;

    CUdeviceptr  d_instance;
    CUDA_CHECK(Malloc( reinterpret_cast<void**>( &d_instance ), sizeof(OptixInstance) ) );
    CUDA_CHECK(Memcpy( reinterpret_cast<void*>( d_instance ), &instance, sizeof(OptixInstance), cudaMemcpyHostToDevice) );

    OptixBuildInput instance_input;
    instance_input.type                       = OPTIX_BUILD_INPUT_TYPE_INSTANCES;
    instance_input.instanceArray.instances    = d_instance;
    instance_input.instanceArray.numInstances = 1;

    OptixAccelBuildOptions accel_options = {};
    accel_options.buildFlags                  = OPTIX_BUILD_FLAG_ALLOW_UPDATE;
    accel_options.operation                   = OPTIX_BUILD_OPERATION_BUILD;

    OptixAccelBufferSizes buffer_size;

    OPTIX_CHECK( optixAccelComputeMemoryUsage(optixContext, &accel_options, &instance_input, 1, &buffer_size) );
    
    CUDABuffer tempBuffer;
    tempBuffer.alloc(buffer_size.tempSizeInBytes);
  
    iasBuffer.alloc(buffer_size.outputSizeInBytes);

    OPTIX_CHECK(optixAccelBuild(
                optixContext,
                nullptr,             // CUDA stream
                &accel_options,
                &instance_input,
                1,                  // num build inputs
                tempBuffer.d_pointer(),
                buffer_size.tempSizeInBytes,
                iasBuffer.d_pointer(),
                buffer_size.outputSizeInBytes,
                &iasHandle,
                nullptr,            // emitted property list
                0                   // num emitted properties
                ));

    CUDA_SYNC_CHECK();

    tempBuffer.free();

    return iasHandle;
}

I then attempt to update the existing instance launch before the start of each render, but Optix returns an error stating that the “numAabbs” in “buildInputs[0].instanceArray” is not equal to the “numInstances(1)”:

void SampleRenderer::updateInstanceAccel()
{

OptixInstance instance;
memcpy(instance.transform, launchParams.meshTransform, sizeof(float)*12);
instance.instanceId = 0;
instance.visibilityMask = 255;
instance.sbtOffset = 0;
instance.flags = OPTIX_INSTANCE_FLAG_NONE;
instance.traversableHandle = meshTraversable;

CUdeviceptr  d_instance;
CUDA_CHECK(Malloc( reinterpret_cast<void**>( &d_instance ), sizeof(OptixInstance) ) );
CUDA_CHECK(Memcpy( reinterpret_cast<void*>( d_instance ), &instance, sizeof(OptixInstance), cudaMemcpyHostToDevice) );

CUDA_SYNC_CHECK();

OptixBuildInput instance_input;
instance_input.type                       = OPTIX_BUILD_INPUT_TYPE_INSTANCES;
instance_input.instanceArray.instances    = d_instance;
instance_input.instanceArray.numInstances = 1;

OptixAccelBuildOptions accel_options = {};
accel_options.buildFlags                  = OPTIX_BUILD_FLAG_ALLOW_UPDATE;
accel_options.operation                   = OPTIX_BUILD_OPERATION_UPDATE;

OptixAccelBufferSizes buffer_size;

OPTIX_CHECK( optixAccelComputeMemoryUsage(optixContext, &accel_options, &instance_input, 1, &buffer_size) );

CUDABuffer tempBuffer;
tempBuffer.alloc(buffer_size.tempSizeInBytes);

OPTIX_CHECK(optixAccelBuild(
            optixContext,
            nullptr,             // CUDA stream
            &accel_options,
            &instance_input,
            1,                  // num build inputs
            tempBuffer.d_pointer(),
            buffer_size.tempSizeInBytes,
            iasBuffer.d_pointer(),
            buffer_size.outputSizeInBytes,
            &iasHandle,
            nullptr,            // emitted property list
            0                   // num emitted properties
            ));

CUDA_SYNC_CHECK();

tempBuffer.free();
}

Does anyone have any suggestions on what may be causing this error in the transform update? I appreciate any assistance you can provide.

Hi @tbobrow1,

Hey I don’t see anything immediately. You should be able to do an update operation on your IAS without issues. It looks like you have a single mesh, is that correct? The number of instances is not changing, and all the IAS build options are exactly the same during update as they were during the original build?

Which driver version are you using?

You wouldn’t happen to have your modified version of the texture viewer sample checked into github would you? If possible, I will try to reproduce.


David.