// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: MPL-2.0

#include <openvdb/tools/LevelSetSphere.h> // replace with your own dependencies for generating the OpenVDB grid
#include <nanovdb/util/OpenToNanoVDB.h> // converter from OpenVDB to NanoVDB (includes NanoVDB.h and GridManager.h)
#include <nanovdb/util/CudaDeviceBuffer.h>
#include <nanovdb/util/NodeManager.h>

extern "C" void launch_kernels(const nanovdb::NodeManager<float>*,
                               const nanovdb::NodeManager<float>*,
                               cudaStream_t stream);

/// @brief This examples depends on OpenVDB, NanoVDB and CUDA.
int main()
{
    try {
        cudaStream_t stream; // Create a CUDA stream to allow for asynchronous copy of pinned CUDA memory.
        cudaStreamCreate(&stream);

        // Create an OpenVDB grid of a sphere at the origin with radius 100 and voxel size 1.
        auto srcGrid = openvdb::tools::createLevelSetSphere<openvdb::FloatGrid>(100.0f, openvdb::Vec3f(0.0f), 1.0f);

        // Converts the OpenVDB to NanoVDB and returns a GridHandle that uses CUDA for memory management.
        auto gridHandle = nanovdb::openToNanoVDB<nanovdb::CudaDeviceBuffer>(*srcGrid);
        gridHandle.deviceUpload(stream, false); // Copy the NanoVDB grid to the GPU asynchronously
        auto* grid = gridHandle.grid<float>(); // get a (raw) pointer to a NanoVDB grid of value type float on the CPU
        auto* deviceGrid = gridHandle.deviceGrid<float>(); // get a (raw) pointer to a NanoVDB grid of value type float on the GPU
        if (!deviceGrid || !grid) {
            throw std::runtime_error("GridHandle did not contain a grid with value type float");
        }

        auto nodeHandle = nanovdb::createNodeManager<float, nanovdb::CudaDeviceBuffer>(*grid);
        nodeHandle.deviceUpload(deviceGrid, stream, false);
        auto *nodeMgr = nodeHandle.template mgr<float>();
        auto *deviceNodeMgr = nodeHandle.template deviceMgr<float>();
        if (!deviceNodeMgr || !nodeMgr) {
            throw std::runtime_error("NodeManagerHandle did not contain a grid with value type float");
        }

        launch_kernels(deviceNodeMgr, nodeMgr, stream); // Call a host method to print a grid value on both the CPU and GPU

        cudaStreamDestroy(stream); // Destroy the CUDA stream
    }
    catch (const std::exception& e) {
        std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
    }
    return 0;
}