/* -----------------------------------------------------------------------
   See COPYRIGHT.TXT and LICENSE.TXT for copyright and license information
   ----------------------------------------------------------------------- */
#ifndef _cuda_kernel_util_h_
#define _cuda_kernel_util_h_
//
// NOTE: Cannot be included in C or C++ files due to
//       special CUDA types such as int4, dim3, etc.
//       Can only be included in CUDA files.
//
// NOTE: __device__ function definitions live in
//       cuda_kernel_util.inc due to nvcc limitations.

#include "plm_config.h"
#include <cuda.h>
#include "plm_int.h"

#define GRID_LIMIT_X 65535
#define GRID_LIMIT_Y 65535

// JAS 2010.12.09
// Frequently used thread based indexing methods
// My attempt at standardizing CUDA threading across plastimatch (pls use these!)
#define block_size        (blockDim.x * blockDim.y * blockDim.z)
#define block_idx         ((gridDim.x * blockIdx.y) + blockIdx.x)
#define thread_idx_local  ((((blockDim.y * threadIdx.z) + threadIdx.y) * blockDim.x) + threadIdx.x)
#define thread_idx_global ((block_idx * block_size) + thread_idx_local)

typedef struct cuda_timer_struct cuda_timer;
struct cuda_timer_struct {
    cudaEvent_t start;
    cudaEvent_t stop;
};

template <typename T>
__device__ inline void
shared_memset (T* s, T c, int n);


__device__ inline void
atomic_add_float (
    float* addr,
    float val
);

template <typename T>
__device__ inline void
stog_memcpy (
    T* global,
    T* shared,
    int set_size
);

void CUDA_malloc_3d_array (
    cudaArray_t *dev_array,
    const plm_long *dim
);

void CUDA_clear_3d_array (
    cudaSurfaceObject_t *surf,
    const plm_long *dim
);

#if defined __cplusplus
extern "C" {
#endif

    plmcuda_EXPORT (
        void CUDA_array2vec_int3, 
        int3* vec,
        plm_long* array
    );

    plmcuda_EXPORT (
        void CUDA_array2vec_float3, 
        float3* vec,
        float* array
    );

    plmcuda_EXPORT (
    int CUDA_exec_conf_1tpe,
        dim3 *dimGrid,          /* OUTPUT: Grid  dimensions */
        dim3 *dimBlock,         /* OUTPUT: Block dimensions */
        int num_threads,        /* INPUT: Total # of threads */
        int threads_per_block,  /* INPUT: Threads per block  */
        bool negotiate          /* INPUT: Is threads per block negotiable? */
    );

    plmcuda_EXPORT (
    void CUDA_exec_conf_1bpe,
        dim3 *dimGrid,           /* OUTPUT: Grid  dimensions   */
        dim3 *dimBlock,          /* OUTPUT: Block dimensions   */
        int num_blocks,          /*  INPUT: Number of blocks   */
        int threads_per_block    /*  INPUT: Threads per block  */
    );

    plmcuda_EXPORT (
    void CUDA_timer_start,
        cuda_timer *timer
    );

    plmcuda_EXPORT (
    float CUDA_timer_report,
        cuda_timer *timer
    );

    plmcuda_EXPORT (
        void CUDA_memcpy_to_3d_array,
        cudaArray_t *dev_array,
        const plm_long *dim,
        const float *source
    );

    plmcuda_EXPORT (
        void CUDA_memcpy_from_3d_array,
        float *dest, 
        const plm_long *dim,
        cudaArray_t *dev_array
    );

    plmcuda_EXPORT (
        void CUDA_bind_texture,
        cudaTextureObject_t *tex_obj,
        cudaArray_t dev_array
    );

    plmcuda_EXPORT (
        void CUDA_bind_surface,
        cudaSurfaceObject_t *surf,
        cudaArray_t dev_array
    );

#if defined __cplusplus
}
#endif

#endif
