void StructuredVolume::getVolumeFromMemory() 
  {
    //! Create the equivalent ISPC volume container and allocate memory for voxel data.
    createEquivalentISPC();

    //! Get a pointer to the source voxel data.
    const Data *voxelData = getParamData("voxelData", NULL);  
    exitOnCondition(voxelData == NULL, "no voxel data specified");  

    const uint8 *data = (const uint8 *) voxelData->data;

    //! The dimensions of the source voxel data and target volume must match.
    exitOnCondition(size_t(volumeDimensions.x) * volumeDimensions.y * volumeDimensions.z != voxelData->numItems, "unexpected source voxel data dimensions");

    //! The source and target voxel types must match.
    exitOnCondition(getVoxelType() != voxelData->type, "unexpected source voxel type");

    //! Size of a volume slice in bytes.
    size_t sliceSizeInBytes = volumeDimensions.x * volumeDimensions.y * getVoxelSizeInBytes();

    //! Copy voxel data into the volume in slices to avoid overflow in ISPC offset calculations.
    for (size_t z=0 ; z < volumeDimensions.z ; z++) 
      setRegion(&data[z * sliceSizeInBytes], vec3i(0, 0, z), 
                vec3i(volumeDimensions.x, volumeDimensions.y, 1));

  }
  void LinearTransferFunction::commit()
  {
    // Create the equivalent ISPC transfer function.
    if (ispcEquivalent == NULL) createEquivalentISPC();

    // Retrieve the color and opacity values.
    colorValues   = getParamData("colors", NULL);  
    opacityValues = getParamData("opacities", NULL);

    // Set the color values.
    if (colorValues) 
      ispc::LinearTransferFunction_setColorValues(ispcEquivalent, 
                                                  colorValues->numItems, 
                                                  (ispc::vec3f *) colorValues->data);

    // Set the opacity values.
    if (opacityValues) {
      float *alpha = (float *)opacityValues->data;
      ispc::LinearTransferFunction_setOpacityValues(ispcEquivalent, 
                                                    opacityValues->numItems, 
                                                    (float *)opacityValues->data);
    }

    // Set the value range that the transfer function covers.
    vec2f valueRange = getParam2f("valueRange", vec2f(0.0f, 1.0f));  
    ispc::TransferFunction_setValueRange(ispcEquivalent, (const ispc::vec2f &) valueRange);

    // Notify listeners that the transfer function has changed.
    notifyListenersThatObjectGotChanged();
  }
  int GhostBlockBrickedVolume::setRegion(
      // points to the first voxel to be copied. The voxels at 'source' MUST
      // have dimensions 'regionSize', must be organized in 3D-array order, and
      // must have the same voxel type as the volume.
      const void *source,
      // coordinates of the lower, left, front corner of the target region
      const vec3i &regionCoords,
      // size of the region that we're writing to, MUST be the same as the
      // dimensions of source[][][]
                                    const vec3i &regionSize)
  {
    // Create the equivalent ISPC volume container and allocate memory for voxel
    // data.
    if (ispcEquivalent == nullptr)
      createEquivalentISPC();

    /*! \todo check if we still need this 'computevoxelrange' - in
        theory we need this only if the app is allowed to query these
        values, and they're not being set in sharedstructuredvolume,
        either, so should we actually set them at all!? */
    // Compute the voxel value range for unsigned byte voxels if none was
    // previously specified.
    Assert2(source,"nullptr source in GhostBlockBrickedVolume::setRegion()");

#ifndef OSPRAY_VOLUME_VOXELRANGE_IN_APP
    if (findParam("voxelRange") == NULL) {
      // Compute the voxel value range for float voxels if none was
      // previously specified.
      const size_t numVoxelsInRegion
        = (size_t)regionSize.x *
        + (size_t)regionSize.y *
        + (size_t)regionSize.z;
      if (voxelType == "uchar")
        computeVoxelRange((unsigned char *)source, numVoxelsInRegion);
      else if (voxelType == "ushort")
        computeVoxelRange((unsigned short *)source, numVoxelsInRegion);
      else if (voxelType == "float")
        computeVoxelRange((float *)source, numVoxelsInRegion);
      else if (voxelType == "double")
        computeVoxelRange((double *) source, numVoxelsInRegion);
      else {
        throw std::runtime_error("invalid voxelType in "
                                 "GhostBlockBrickedVolume::setRegion()");
      }
    }
#endif
    // Copy voxel data into the volume.
    const int NTASKS = regionSize.y * regionSize.z;

    parallel_for(NTASKS, [&](int taskIndex){
        ispc::GBBV_setRegion(ispcEquivalent,
                             source,
                             (const ispc::vec3i &)regionCoords,
                             (const ispc::vec3i &)regionSize,
                             taskIndex);
    });

    return true;
  }