RenderKit / ospray

An Open, Scalable, Portable, Ray Tracing Based Rendering Engine for High-Fidelity Visualization
http://ospray.org
Apache License 2.0
1k stars 182 forks source link

Question on new ospNewData signature #363

Closed paulmelis closed 4 years ago

paulmelis commented 4 years ago

In bcecfe6577bad4b0a689196a3d6d485d1c4e1dd4 ospNewData was changed from

OSPRAY_INTERFACE OSPData ospNewData(size_t numItems,
                                      OSPDataType,
                                      const void *source,
                                      uint32_t dataCreationFlags OSP_DEFAULT_VAL(0));

to

OSPRAY_INTERFACE OSPData ospNewData(OSPDataType,
      uint32_t numItems1,
      uint32_t numItems2 OSP_DEFAULT_VAL(1),
      uint32_t numItems3 OSP_DEFAULT_VAL(1));

But in, for example, apps/tutorials/ospTutorialStructuredVolume.cpp I see calls like https://github.com/ospray/ospray/blob/9527f27984f6ba7daeb3d64df7c0d4615866ddab/apps/tutorials/ospTutorialVolumePathTracer.cpp#L189

How can that work (and compile) given the uint32_t parameters on ospNewData? Shouldn't that give precision errors on 64-bit systems due to a pointer having more bits than an uint32_t?

I'm mostly trying to understand why it works :)

paulmelis commented 4 years ago

Ah, found the reason, ospray_util.h defines the previous version of the call:

https://github.com/ospray/ospray/blob/9527f27984f6ba7daeb3d64df7c0d4615866ddab/ospray/include/ospray/ospray_util.h#L105-L110

So the above API is going away, meaning that for cases where you want to pass data that you don't want to keep allocated in the application you need to do something new. The porting guide alludes to new APIs ospNewData (opaque mem allocated by OSPRay) and ospCopyData. So would I need to do something like this to get data into an OSPRay-managed data array using a temporary shared OSPData array?

std::vector<float> vertices;
// fill vertex array
int nv = vertices.size()/3;

OSPData data = ospNewSharedData1D(vertices.data(), OSP_VEC3F, nv);
OSPData managed = ospNewData1D(OSP_VEC3F, nv);
ospCopyData1D(data, managed, 0);
ospRelease(data);
ospSetData(geometry, "vertex.position", managed);
ospRelease(managed);

I'm curious: as it's possible to directly share a block of memory with OSPRay I assume no copy of the data is made underwater to a managed array in order for the actual rendering device to its thing? Otherwise, why not always use the two-step process above? And would it not be possible to directly access the data array allocated by a ospNewData1D to avoid having to create the shared array?

paulmelis commented 4 years ago

With respect to the explanation in the porting guide of

ospSetObjectAsData(group, "geometry", OSP_GEOMETRIC_MODEL, model);

being equal to

OSPData geometricModels = ospNewSharedData1D(&model, OSP_GEOMETRIC_MODEL, 1);
ospSetObject(group, "geometry", geometricModels);
ospRelease(geometricModels);

The use of ospNewSharedData1D is a bit confusing as it suggests that &model must keep pointing to a valid object even after ospSetObjectAsData() has returned (as the lifetime of the geometricModels array isn't exactly known).

But looking at https://github.com/ospray/ospray/blob/9527f27984f6ba7daeb3d64df7c0d4615866ddab/ospray/api/ospray_util_impl.cpp#L156-L164

and

https://github.com/ospray/ospray/blob/9527f27984f6ba7daeb3d64df7c0d4615866ddab/ospray/api/ospray_util_impl.cpp#L182-L196

the actual behaviour of ospSetObjectAsData is to copy into an opaque array

    OSPData src = ospNewSharedData(&model, OSP_GEOMETRIC_MODEL, 1);
    OSPData data = ospNewData(OSP_GEOMETRIC_MODEL, 1);
    ospCopyData(src, data);
    ospRelease(src);
    ospSetObject(group, "geometry", data); 
    ospRelease(data); 

so the normal reference counting ways can be used. Which is what I see here

https://github.com/ospray/ospray/blob/9527f27984f6ba7daeb3d64df7c0d4615866ddab/apps/ospray_testing/geometry/Boxes.cpp#L84-L89

I'm actually not even sure I convinced myself with the above explanation but I'm trying to get my head around the ospNewSharedData1D equivalence :grin:

jeffamstutz commented 4 years ago

We ended up settling on 64-bit sizes for data arrays for v2.x.

Shared data represents memory from the application that OSPRay can read, which is trivially cheap to call. However, the constraint is that the application must keep that memory around as long as the OSPRay data looking at it is also valid. Opaque data objects allow these lifetimes to be separated, but populating the opaque data requires copying from a shared data (i.e. the shared data just describes the input, which could be strided!).

Hope this helps anyone reading this!