This directory contains the ANARI frontend library (libanari
) which is the
core C API which applications directly use. This document will outline the
details of what is necessary in order to implement a device, as using the
API itself is detailed at length in the ANARI specification.
The primary function of this library is to dispatch
ANARI C API function calls to the corresponding ANARI
devcie implementing the API (designated by an ANARIDevice
handle in the
functioin signature). There are two core concepts that implementations must
implement, along with optional additional tools which can aid in implementing
various ANARI features.
The headers used to implement the required components of a device for this frontend library are found in the backend directory of the core installed headers. Implementations are primarily expected to build their devices against an installation of the ANARI-SDK.
The first required item that must be implemented is ANARILibrary
. This is done
by subclassing LibraryImpl
and
providing the required method definitions. LibraryImpl
acts as the concrete
representation of ANARILibrary
, where the frontend library uses this class to
manage all C API calls which are done on ANARILibrary
(where the function's
first argument is the library).
Note that anariLoadLibrary()
uses the following naming convention for
dynamically loading an ANARILibrary
at runtime: anari_library_[name]
, where
[name]
matches the string passed to anariLoadLibrary
. [name]
is used to
construct a C function name which is looked up as the entry point needed to
create an instance of LibraryImpl
. Implementations should use the
ANARI_DEFINE_LIBRARY_ENTRYPOINT
macro at the bottom of LibraryImpl.h
to
define this entrypoint function, where it is necessary to match the first macro
argument with [name]
in the physical shared library file. For example, the
provided helide
device on linux compiles into the shared library
libanari_library_helide.so
, and helide
is the first argument to
ANARI_DEFINE_LIBRARY_ENTRYPOINT
, as seen here.
It is possible to directly construct a device if client applications directly
links your library itself, but it is highly recommended to always provide the
dynamic path via anariLoadLibrary()
and LibraryImpl
as this is the most
common way to create ANARI devices. The alternate method of directly
constructing a device via linking is show by the
anariTutorialDirectLink
sample app which includes a custom header created by helide
to directly
create an instance of the helide
device.
The majority of the ANARI C API is implemented by subclassing
DeviceImpl
. Similar to implementing
ANARILibrary
, ANARIDevice
directly represents an instance of DeviceImpl
where the methods of the class correspond to functions in the ANARI API handled
by the device (where the function's first argument is the device). Device
implementations should always seek to make each instance of DeviceImpl
independent from each other by avoiding any shared state between them (i.e.
static state within the class or shared library).
Almost the entirety of DeviceImpl
directly corresponds to functions found
in anari.h
. The only state held by DeviceImpl
are
the default status callback and callback user pointer. DeviceImpl
is intended
to be very minimal -- implementors who desire SDK-provided implementations of
much of the API should use the helium
layer which implements many
common concepts, but requires implementations to opt-in to various helium
abstractions and classes. The provided helide
device demonstrates
ultimately how to implement DeviceImpl
through using
helium::BaseDevice
. Device implementors can use the
sum of helium::BaseDevice
and
helide::HelideDevice
as a full example of
implementing ANARI. Further documentation of what functionality helium
provides can be found in its README.
Devices should implement the device and object queries offered by the ANARI API in order for applications to introspect what extensions (and their details) are offered. However, these functions can be quite tedius and repetitive to implement, so Python-based code generation tools are offered to let library and device authors minimize the effort in writing them. They use JSON definition files to generate C++ which can then be used to implement the various query functions.
All of the Python tooling is installed when INSTALL_CODE_GEN_SCRIPTS
is
enabled when the SDK is installed. This can then be consumed by including the
code_gen
component name in find package:
find_package(anari REQUIRED COMPONENTS code_gen)
This brings the anari_generate_queries()
CMake function into scope downstream,
which has the following signature:
anari_generate_queries(
NAME [device_name]
PREFIX [device_prefix]
CPP_NAMESPACE [namespace]
JSON_DEFINITIONS_FILE [path/to/device/definitions.json]
)
This CMake function will create a target called generate_[device_name]_device
,
which must be built manually in order to generate any C++. By invoking this
targets, a [device_prefix]Queries.cpp/.h
are created, which can be included
in the local device build's source. Please refer to helide as an
example of how these components all go together.
Note that all core spec extensions are defined in a collection of JSON files that are referenced in the downstream JSON definitions. It is recommened to copy an existing JSON definitions file and modify it accordingly.