/******************************************************************************
 * Copyright 2018 NVIDIA Corporation. All rights reserved.
 *****************************************************************************/
/// \file
/// \brief Scene attribute representing user programmable rendering kernel
///        components such as surface intersection programs or volume sample programs.

#ifndef NVIDIA_INDEX_IRENDERING_KERNEL_PROGRAMS_H
#define NVIDIA_INDEX_IRENDERING_KERNEL_PROGRAMS_H

#include <mi/dice.h>

#include <nv/index/iattribute.h>

namespace nv
{
namespace index
{

/// @ingroup nv_index_scene_description_attribute
/// The interface class representing user-programmable rendering kernel components.
///
/// There are two sub-classes from this interface representing rendering kernel programs applied to
/// volumetric
/// primitives, such as regular volumes (\c IVolume_sample_program), and applied to surface
/// primitives, such as
/// regular heightfields or planes (\c ISurface_sample_program). These classes act as scene
/// attributes to the
/// particular scene elements.
///
/// These programs are not compiled in-place but on each rendering node depending on the encountered
/// GPU-devices and
/// their particular compute capabilities. Failure to compile a rendering kernel program results in
/// an error
/// containing the compile log. In order to indicate such a failure to the user, the primitives the
/// particular program
/// attribute is applied to are rendered in a signal color (deep-pink color).
///
/// \note For best performance of the compiled rendering kernels, methods defined in device code
/// need to be
/// decorated with special qualifiers. The qualifiers required by NVIDIA IndeX are covered by the
/// \c NV_IDX_DEVICE_INLINE_MEMBER macro. While it is not strictly required to use this macro, it is
/// advised to do so.
///
class IRendering_kernel_program
  : public mi::base::Interface_declare<0x4953ee6c, 0x1099, 0x47de, 0x94, 0xa8, 0x5e, 0xb7, 0x17,
      0xe4, 0x83, 0x7d, nv::index::IAttribute>
{
public:
  /// Sets the source code for the rendering kernel program.
  ///
  /// \param[in] prog_src     String containing the program source code.
  ///
  virtual void set_program_source(const char* prog_src) = 0;

  /// Returns the source code for the rendering kernel program.
  ///
  /// \return String containing the program source code.
  ///
  virtual const char* get_program_source() const = 0;
};

/// @ingroup nv_index_scene_description_attribute
/// The interface class representing user-defined parameter buffers for user-programmable rendering
/// kernel components.
///
/// User-defined parameter buffers allow the application to define custom input to \c
/// IRendering_kernel_program
/// instances. Such input can be utilized to implement custom rendering kernel programs independent
/// of the input made
/// available through the NVIDIA IndeX rendering kernel interfaces. For instance, an application is
/// enabled to
/// implement a specialized material shading method that is not pre-defined by any existing NVIDIA
/// IndeX rendering
/// properties. The required material parameters, unknown to the actual rendering system, are passed
/// through this
/// interface class to NVIDIA IndeX, which handles the distribution of such data to each rendering
/// node and rendering
/// device. The interface class acts as a scene attribute to scene elements enabled to utilize
/// rendering kernel components
/// (\c IRendering_kernel_program).
///
/// Each instance of this interface allows for a certain number of data buffers to be added. The
/// buffer data is
/// assigned to so called buffer slots. These buffer slots are a way to identify the actual buffers
/// and access them
/// in the rendering kernel programs, as shown in the following example code snippet of a volume
/// sample program.
///
/// This example illustrates the input of clip-plane parameters on the application side:
///
/// \code
/// struct Plane_data_struct
/// {
///     mi::math::Vector<mi::Float32, 4>   plane_equation;
/// };
///
/// // Define two clip planes
/// Plane_data_struct planes[2];
///
/// // First clip plane
/// mi::math::Vector<mi::Float32, 3> clip_plane_0_normal = mi::math::Vector<mi::Float32, 3>(1.0f,
/// 1.0f, -1.0f);
/// clip_plane_0_normal.normalize();
/// planes[0].plane_equation = mi::math::Vector<mi::Float32, 4>(clip_plane_0_normal, -11.0f);
///
/// // Second clip plane
/// mi::math::Vector<mi::Float32, 3> clip_plane_1_normal = mi::math::Vector<mi::Float32, 3>(-1.0f,
/// 1.0f, 1.0f);
/// clip_plane_1_normal.normalize();
/// planes[1].plane_equation = mi::math::Vector<mi::Float32, 4>(clip_plane_1_normal, 10.0f);
///
/// // Add the two defined planes to the rendering kernel parameter buffers
/// kernel_param_buffers->set_buffer_data(CLIP_PLANE_PARAM_BUFFER_SLOT, planes,
/// sizeof(Plane_data_struct) * 2);
/// \endcode
///
/// This example illustrates how to access clip-plane information defined by the application in a
/// rendering kernel
/// program:
///
/// \code
/// // Define the user-defined data structure
/// struct Plane_data_buffer
/// {
///    float4 plane_equation;
/// };
///
/// class NV_IDX_volume_sample_program
/// {
///     NV_IDX_VOLUME_SAMPLE_PROGRAM
///
/// public:
///     // Define variables to bind the contents of the user-defined buffer to
///     const Plane_data_buffer*  m_plane_array_buffer; ///< access the entire array of user data
///     const Plane_data_buffer*  m_plane_buffer_0;     ///< access a specific entry of the user
///     data
///     const Plane_data_buffer*  m_plane_buffer_1;
///
/// public:
///     NV_IDX_DEVICE_INLINE_MEMBER
///     void init_instance()
///     {
///         // Bind the contents of the buffer slot 0 to the variable
///         m_plane_array_buffer = NV_IDX_bind_parameter_buffer<Plane_data_buffer>(0);
///
///         // Bind the contents of the buffer slot 0 to the variable pointing to the first entry
///         m_plane_buffer_0 = NV_IDX_bind_parameter_buffer<Plane_data_buffer>(0, 0 /*array
///         offset*/);
///         // Bind the contents of the buffer slot 0 to the variable pointing to the second entry
///         m_plane_buffer_1 = NV_IDX_bind_parameter_buffer<Plane_data_buffer>(0, 1 /*array
///         offset*/);
///     }
/// ...
///     NV_IDX_DEVICE_INLINE_MEMBER
///     int execute(
///         const float3&   sample_position,
///               float4&   output_color)
///     {
///         const float dist_p_00 = dot(m_plane_array_buffer[0].plane_equation,
///         make_float4(sample_position, 1.0f));
///         const float dist_p_01 = dot(m_plane_array_buffer[1].plane_equation,
///         make_float4(sample_position, 1.0f));
/// \endcode
///
/// or to access the same data through the separate entries:
///
/// \code
///         const float dist_p_00 = dot(m_plane_buffer_0->plane_equation,
///         make_float4(sample_position, 1.0f));
///         const float dist_p_01 = dot(m_plane_buffer_1->plane_equation,
///         make_float4(sample_position, 1.0f));
/// ...
/// };
/// \endcode
///
/// As shown in the above example the association of data input through this interface class and the
/// actual data
/// at runtime happens through the buffer slot identifiers. Further, the example illustrates that a
/// buffer slot can
/// actually hold an array of structures (in this example an array of \c Plane_data_buffer
/// instances). It is possible
/// to access such arrays in a straightforward way through the array index operator or by binding
/// the contents of the
/// array to individual variables. The function to perform the binding (\c
/// NV_IDX_bind_parameter_buffer()) allows both use
/// cases:
///
/// \code
/// NV_IDX_DEVICE_INLINE_MEMBER
/// const T* NV_IDX_bind_parameter_buffer(
///             unsigned buffer_slot,
///             unsigned buffer_slot_offset = 0u);
/// \endcode
///
/// The \c buffer_slot_offset parameter allows to select a particular array element. The offset unit
/// is the actual
/// number of array elements to offset the binding.
///
/// It is important to note that the contents of the buffer data input through this interface must
/// conform to certain
/// rules to ensure data consistency between the host and device data. Especially, data alignment of
/// buffer contents
/// must be regarded. For example, certain vector data types require alignment to special boundaries
/// inside structures:
/// http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#vector-types__alignment-requirements-in-device-code
///
/// Example for misaligned data between host and device:
///
/// \code
/// struct wrong_alignment
/// {
///     float3  normal;
///     float4  color; // misaligned
/// };
/// \endcode
///
/// In this example the 'color' entry of the structure is not correctly aligned to the struct. A \c
/// float4 variable requires
/// a 4 byte alignment. Such cases can be corrected by manually adding padding data:
///
/// \code
/// struct correct_alignment
/// {
///     float3  normal;
///     char    padding_data[4]; // align the next entry to a 4 byte boundary
///     float4  color;
/// };
/// \endcode
///
/// Similarly, when storing multiple elements in an array of structures, the individual structures
/// require alignment to
/// a certain boundary. This required array offset alignment can be queried through this interface
/// using the
/// \c get_array_offset_alignment() method. In order to achieve correct structure alignment, padding
/// data can be used:
///
/// \code
/// struct array_element
/// {
///     float material_property_0;
///     float material_property_1;
///     float material_property_2;
///     char  padding[4]; // pad structure to multiple of 16 bytes
/// };
/// \endcode
///
class IRendering_kernel_program_parameters
  : public mi::base::Interface_declare<0x82ad1cdf, 0xcbac, 0x452f, 0x9b, 0x63, 0x13, 0xe4, 0x94,
      0xc9, 0xbf, 0xaa, nv::index::IAttribute>
{
public:
  /// Returns the number of valid buffer slots available to assign user-defined data to.
  ///
  /// \returns Returns the number of valid buffer slots available.
  ///
  virtual mi::Uint32 get_nb_buffer_slots() const = 0;

  /// Returns the maximum size of an individual buffer assignable to an individual buffer slot.
  ///
  /// \returns Maximum size of an individual buffer assignable to a buffer slot.
  ///
  virtual mi::Size get_max_buffer_size() const = 0;

  /// Returns the alignment in bytes of array components in a single buffer assigned to a buffer
  /// slot.
  ///
  /// \returns Alignment in bytes of array components in a single buffer.
  ///
  virtual mi::Size get_array_offset_alignment() const = 0;

  /// Assigns the buffer contents to a particular buffer slot.
  ///
  /// \note       An instance of the \c IRendering_kernel_program_parameters class does not take
  /// ownership of the
  ///             user data passed through the \c set_buffer_data() method. The buffer data is
  ///             copied for internal use.
  ///
  /// \param[in]  slot_idx   The buffer slot index to assign the buffer data to.
  /// \param[in]  data       A raw pointer to the buffer data to assign to the buffer slot.
  /// \param[in]  data_size  The size in bytes of the buffer data to assign to the buffer slot.
  ///
  /// \returns    True on successful input of the user buffer data to the specified buffer slot,
  /// false otherwise.
  ///
  virtual bool set_buffer_data(mi::Uint32 slot_idx, const void* data, mi::Size data_size) = 0;
};

/// @ingroup nv_index_scene_description_attribute
/// Scene attribute to map scene elements to slots that are accessible by user-programmable
/// rendering kernel components.
///
/// The attribute is similar to \c IRendering_kernel_program_parameters, but instead of providing a
/// user-defined buffer, it provides a fixed number of \e slots, where scene elements can be mapped
/// to. These scene elements (e.g., \c IRegular_volume can then be accessed inside a
/// user-defined program using \c NV_IDX_bind_scene_element() together with the \e state.
///
/// For example, consider the mapping attribute is applied to \c IPlane, mapping an existing \c
/// IRegular_volume to slot 1 by calling <tt>mapping_attribute.set_mapping(1, volume_tag)</tt>. A \c
/// IVolume_sample_program also applied to the plane may then access the volume data as follows:
///
/// \code
/// class NV_IDX_volume_sample_program
/// {
///     NV_IDX_VOLUME_SAMPLE_PROGRAM
/// public:
///     NV_IDX_DEVICE_INLINE_MEMBER
///     void init_instance() {}
///
///     NV_IDX_DEVICE_INLINE_MEMBER
///     int execute(
///         const float3&   sample_position,
///               float4&   output_color)
///     {
///         const int SLOT_ID = 1;
///         const uint volume_id = NV_IDX_bind_scene_element(SLOT_ID);
///         const float v = state.get_volume(volume_id).sample(sample_position);
///         output_color = make_float4(v, v, v, 1.f);
///         return NV_IDX_PROG_OK;
///     }
/// };
/// \endcode
///
/// The scene element that should be mapped to a slot must be part of the scene description. When
/// mapping attributes (\c IAttribute), they must be enabled and also assigned to the scene element
/// to which this mapping attribute is assigned.
///
class IRendering_kernel_program_scene_element_mapping
  : public mi::base::Interface_declare<0x8ecf51a1, 0xdf74, 0x47a1, 0x84, 0x27, 0xae, 0xb0, 0x14,
      0xee, 0x52, 0xc8, nv::index::IAttribute>
{
public:
  /// Returns the number of slots available to assign mappings to.
  ///
  /// \return Number of slots available.
  ///
  virtual mi::Uint32 get_nb_slots() const = 0;

  /// Maps a scene element to a slot.
  ///
  /// \param[in]  slot_idx   Slot index, must be less than \c get_nb_slots().
  /// \param[in]  data_tag   Tag of a scene element. If \c NULL_TAG, an existing mapping is removed.
  ///
  /// \return     True is \c slot_idx is valid.
  ///
  virtual bool set_mapping(mi::Uint32 slot_idx, mi::neuraylib::Tag_struct data_tag) = 0;

  /// Returns the mapping of a given slot.
  ///
  /// \param[in]  slot_idx   Slot index, must be less than \c get_nb_slots().
  ///
  /// \return     Tag of the scene element that is mapped to the slot, or \c NULL_TAG is non is
  ///             mapped or the slot index is invalid.
  ///
  virtual mi::neuraylib::Tag_struct get_mapping(mi::Uint32 slot_idx) const = 0;
};

/// @ingroup nv_index_scene_description_attribute
/// An interface class representing rendering kernel programs applied to volume primitives (e.g., \c
/// IRegular_volume).
///
/// The programs applied to volumes are evaluated for each sample taken during the rendering
/// process. An example of a
/// volume sample program is:
///
/// \code
/// class NV_IDX_volume_sample_program
/// {
///     NV_IDX_VOLUME_SAMPLE_PROGRAM
///
/// public:
///     NV_IDX_DEVICE_INLINE_MEMBER
///     void init_instance() {}
///
///     NV_IDX_DEVICE_INLINE_MEMBER
///     int execute(
///         const float3&   sample_position, // read-only
///               float4&   output_color)    // write-only
///     {
///         // Scalar volume sample
///         const float  volume_sample = state.volume.sample(sample_position);
///         // Look up color and opacity values from colormap
///         const float4 sample_color  = state.colormap.lookup(volume_sample);
///
///         if (0.3f < volume_sample && volume_sample < 0.7f)
///         {
///             return NV_IDX_PROG_DISCARD_SAMPLE;
///         }
///
///         // Output swizzled color
///         output_color = make_float4(sample_color.y,
///                                    sample_color.z,
///                                    sample_color.x,
///                                    sample_color.w);
///
///         return NV_IDX_PROG_OK;
///     }
/// };
/// \endcode
///
/// Currently the class representing a volume sample program needs to follow the template set in the
/// example above. It
/// has to be called \c NV_IDX_volume_sample_program and is required to expose two public methods
/// called
/// \c init_instance(), which is executed once per rendering instance of a subregion, and \c
/// execute(),
/// which is then executed for each sample taken along a viewing ray.
///
/// Each volume sample program is also required to have the \c NV_IDX_VOLUME_SAMPLE_PROGRAM macro as
/// the first
/// definition inside the program class. This macro initializes the internal state of the program
/// exposed to the user.
/// Later revisions of the rendering kernel programs feature will make the definition more
/// straightforward.
///
/// The \c init_instance() method can be used to initialize variables used in the per-sample
/// computation of the
/// \c execute() method. The initializations can encompass the calculation of static data such as
/// lighting or material
/// properties.
///
/// The \c execute() method always receives the current sample position as input and the output
/// color as output. Through
/// the return value of this method the rendering kernel can be informed to use the generated output
/// sample or to
/// discard the current sample. For this purpose there are two return values for the \c execute()
/// method:
/// - \c NV_IDX_PROG_OK
/// - \c NV_IDX_PROG_DISCARD_SAMPLE
///
/// The list of return values may be extended in the future to, for instance, discard entire ray
/// segments.
///
/// Each instance of a volume sample program has access to a state variable allowing access to the
/// current volume data
/// set as well as the current colormap. The contents of this state is derived from the scene
/// definition. The state
/// is accessed through the \c state variable available to each instance. The state contains a \c
/// volume and \c colormap
/// variable, which can be used to sample the volume and lookup color and opacity values from a
/// colormap, as demonstrated
/// in the example above. It is important to note that the return value of the \c sample() method is
/// dependent on the
/// actual volume type. A scalar volume, such as \c uint8 or \c float32 volumes return a simple
/// float value, whereas RGBA-
/// volumes return a \c float4 value.
///
class IVolume_sample_program
  : public mi::base::Interface_declare<0x4c2b9169, 0xb377, 0x452a, 0x8f, 0xfa, 0x87, 0x70, 0x35,
      0xd9, 0xa2, 0xe8, nv::index::IRendering_kernel_program>
{
};

/// @ingroup nv_index_scene_description_attribute
/// An interface class representing rendering kernel programs applied to surface primitives (e.g.,
/// \c IRegular_heightfield).
///
/// The programs applied to surface geometries are evaluated for each surface intersection
/// encountered during the
/// rendering process. An example of a surface sample program is:
///
/// \code
/// class NV_IDX_surface_sample_program
/// {
///     NV_IDX_SURFACE_SAMPLE_PROGRAM
///
///     float3 light_direction;
///     float3 material_ka;     // ambient term
///
/// public:
///     NV_IDX_DEVICE_INLINE_MEMBER
///     void init_instance()
///     {
///         light_direction = normalize(make_float3(1.0f, 1.0f, 1.0f));
///         material_ka     = make_float3(0.1f, 0.1f, 0.1f);
///     }
///
///     NV_IDX_DEVICE_INLINE_MEMBER
///     int execute(
///         const float3&   sample_position, // read-only
///         const float3&   sample_normal,   // read-only
///         const float4&   sample_color,    // read-only
///               float&    sample_distance, // read/write
///               float4&   output_color)    // write-only
///     {
///         if (sample_color.w < 0.1f)
///         {
///             return NV_IDX_PROG_DISCARD_SAMPLE;
///         }
///
///         const float n_l          = dot(sample_normal, light_direction);
///
///         const bool  soft_diffuse = false;
///         const float diffuse      =   (soft_diffuse)
///                                    ? n_l * 0.5f + 0.5f
///                                    : max(0.0f, n_l);
///
///         const float3 kd      = make_float3(sample_color);
///         const float3 s_lit   = material_ka + kd * diffuse;
///
///         output_color = make_float4(s_lit, sample_color.w);
///
///         return NV_IDX_PROG_OK;
///     }
///
/// };
/// \endcode
///
/// Currently the class representing a surface sample program needs to follow the template set in
/// the example above. It
/// has to be called \c NV_IDX_surface_sample_program and is required to expose two public methods
/// called
/// \c init_instance(), which is executed once per rendering instance of a subregion, and execute(),
/// which is then
/// executed for each surface intersection encountered along a viewing ray.
///
/// Each surface sample program is also required to have the \c NV_IDX_SURFACE_SAMPLE_PROGRAM macro
/// as the first
/// definition inside the program class. This macro initializes the internal state of the program
/// exposed to the user.
/// Later revisions of the rendering kernel programs feature will make the definition more
/// straightforward.
///
/// The \c init_instance() method can be used to initialize variables used in the per-intersection
/// computation of the
/// \c execute() method. The initializations can encompass the calculation of static data such as
/// lighting or material
/// properties.
///
/// The \c execute() method always receives the current sample position, normal and color as input
/// and the output color as
/// output. The sample distance is passed as a modifiable reference to the method, which allows
/// surface sample programs
/// to override the intersection distance value individually. This feature can be utilized to
/// implement custom depth-offset
/// techniques to applicable surface geometries. Through the return value of this method the
/// rendering kernel can
/// be informed to use the generated output sample or to discard the current sample. For this
/// purpose there are two
/// return values for the \c execute() method:
/// - \c NV_IDX_PROG_OK
/// - \c NV_IDX_PROG_DISCARD_SAMPLE
///
/// The list of return values may be extended in the future to, for instance, discard entire ray
/// segments.
///
/// In contrast to \c IVolume_sample_program instances, a surface sample program does currently not
/// expose an internal
/// state variable. This is subject to change in future revisions of this feature.
///
class ISurface_sample_program
  : public mi::base::Interface_declare<0x861e254b, 0x6ade, 0x4579, 0x93, 0x15, 0x23, 0x2d, 0x5c,
      0x3f, 0xe5, 0xfd, nv::index::IRendering_kernel_program>
{
};

} // namespace index
} // namespace nv

#endif // NVIDIA_INDEX_IRENDERING_KERNEL_PROGRAMS_H
