// Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#include <OgnTutorialCpuGpuDataDatabase.h>

// This helper file keeps the GPU and CPU functions organized for easy access.
// It's only for illustration purposes; you can choose to organize your functions any way you wish.

// The first algorithm is a simple add, to illustrate passing non-array types.
extern "C" void cpuGpuAddCPU(outputs::sum_t_cpu, inputs::a_t_cpu, inputs::b_t_cpu, size_t);
extern "C" void cpuGpuAddGPU(outputs::sum_t_gpu, inputs::a_t_gpu, inputs::b_t_gpu);

// The second algorithm shows the more common case of accessing array data.
extern "C" void cpuGpuMultiplierCPU(outputs::points_t_gpu, inputs::multiplier_t_gpu, inputs::points_t_gpu, size_t);
extern "C" void cpuGpuMultiplierGPU(outputs::points_t_gpu, inputs::multiplier_t_gpu, inputs::points_t_gpu, size_t);

class OgnTutorialCpuGpuData
{
public:
    static bool compute(OgnTutorialCpuGpuDataDatabase& db)
    {
        // Computing the size of the output is independent of CPU/GPU as the size update is lazy and will happen
        // when the data is requested from the fabric.
        size_t numberOfPoints = db.inputs.points.size();
        db.outputs.points.resize(numberOfPoints);

        // Use the runtime input to determine where the calculation should take place.
        //
        // Another helpful use of this technique is to first measure the size of data being evaluated and
        // if it meets a certain minimum threshold move the calculation to the GPU.
        if (db.inputs.is_gpu())
        {
            cpuGpuAddGPU(db.outputs.sum.gpu(), db.inputs.a.gpu(), db.inputs.b.gpu());

            // GPU data is just raw pointers so the size must be passed down to the algorithm
            cpuGpuMultiplierGPU(
                db.outputs.points.gpu(), db.inputs.multiplier.gpu(), db.inputs.points.gpu(), numberOfPoints);
        }
        else
        {
            // CPU version calls the shared CPU/GPU algorithm directly. This could also be implemented in the
            // CUDA file as an extern "C" function but it is so simple that this is easier.
            cpuGpuAddCPU(db.outputs.sum.cpu(), db.inputs.a.cpu(), db.inputs.b.cpu(), 0);

            // Extract the CPU data and iterate over all of the points, calling the shared deformation algorithm.
            // The data is passed through as raw CUDA-compatible bytes so that the deformation algorithm can live
            // all in one file. If the code were all on the CPU side then the data types could be used as regular
            // arrays, as showing in the tutorial on array data types.
            auto rawOutputPoints = db.outputs.points.cpu().data();
            auto rawInputPoints = db.inputs.points.cpu().data();
            cpuGpuMultiplierCPU(&rawOutputPoints, &db.inputs.multiplier.cpu(), &rawInputPoints, numberOfPoints);
        }

        return true;
    }
};

REGISTER_OGN_NODE()
