-
Notifications
You must be signed in to change notification settings - Fork 57
Home
Welcome to the OWL Wiki - this page for now is mostly to explain how different concepts in OWL work, usually based on questions users had.
One of the key concepts used in OWL is the concept of "variables" to pass information from the host program to the device code. For example, if you want to create a triangle mesh geometry with certain material data, you'll probably want to have "something" on the device where these material parameters etc get stored ... and you'll need a way to set those from the host. (Where they get stored on the device is typically in the shader binding table, BTW, but OWL takes care of that, so you don't have to worry about that).
For now, let's follow this simple example of a triangle mesh with material data. Then, the first thing to do is create a CUDA/C++ class that stores the device-side data - which for our example might look a bit like this:
struct TriMesh {
float3 *vertices;
float3 *normals;
int3 *indices;
struct {
float3 diffuseColor;
cudaTextureObject_t texture;
} material;
};
Second, before you can create any actual geometries of this type, you'll first have to "declare" to OWL what kind of data you're expecting this geometry to have - basically, the size of this struct, and the layout and types of the members. For geometries, you'd do that by first creating a OWLGeomType
that specifies exactly this information (using an array of OWLVarDecl
s to specify the members), for example, as follows:
OWLVarDecl triMeshVars[] = {
{ "vertices", OWL_BUFPTR, OWL_OFFSETOF(TriMesh,vertices) },
...
{ "diffuseColor", OWL_FLOAT3, OWL_OFFSETOF(TriMesh,material.diffuseColor) },
{ "texture", OWL_TEXTURE, OWL_OFFSETOF(TriMesh,material.texture) },
{ nullptr /* sentinel to mark end of list */ }
};
Each variable in this list has a name by which it can be set on the host, a type that tells OWL what type to expect on the device side, and a offset (in bytes) within the struct.
Note:
a) OWL does come with a set of common types pre-specified (eg, OWL_FLOAT, OWL_INT3, etc); most types get simply "copied" to the device, but special rules apply to certain types like buffers and textures, which may been some "translation" from their host-side representation to actual device addresses. In the example above, we have declared the
float3 *verticesmember as a
OWL_BUFPTR, which means that if it gets set as a
OWLBufferon the host, OWL will automatically write the proper address of the device-side data into the struct. Similarly, a "OWL_TEXTURES" gets written as a
OWLTextureon the host, and on the device gets written as a
cudaTextureObject_t`.
b) the names you assign to variables on the host do not have to exactly correspond to those on the device struct, nor do they have to be valid C/C++ identifier names; they can be any names, can even contain special characters, etc. (it is, however, highly recommended to use matching names where possible).
c) there is no type checking on what types you claim variables to have - if, for instance, you declare a variable as OWL_FLOAT
even though on the device it's actually a int
, then you'll get funny results. OWL will do some type checking that what you Set
on a variabe actually matches the type you declared it to be (eg, if you try to owlGeomSetBuffer(...)
on a variable you declared as OWL_FLOAT
, you'll get an error); but OWL cannot know what actual C/C++ types you use on the device.
Now that we know how to declare the members of a struct, we can use that to declare types. Eg, we can create a OWLGeomType
for a OWL geometry that uses the above TriMesh
struct as follows:
OWLGeomType triMeshGT = owlGeomTypeCreate(context,sizeof(TriMeshType), triMeshVars, -1);
Similarly, we could create a raygen program, or launch parameters, using, e.g.,
OWLParams launchParams = owlParamsCreate(context,sizeof(MyLaunchParams), myLaunchParamsVars, -1);
etc. A few notes:
a) you can specify the number of variables in a vardecl explicitly; or - as i've done it in this example - end the list with a {nullptr}
sentinel and specify -1
, which is a bit less error-prone when adding members later on.
b) the sizeof(...)
parameter above specifies the expected size of the type on the device; for geometries, ray-gens, and miss programs this is what OWL will reserve space for in the shader binding table, for launch parameters it is what OWL expects to upload into the "optixLaunchParams" variable.