cmake_minimum_required(VERSION 3.17.5 FATAL_ERROR)
cmake_policy(SET CMP0048 NEW)
cmake_policy(SET CMP0072 NEW)
cmake_policy(SET CMP0074 NEW)

project(TensorRay VERSION 0.1.0 LANGUAGES CXX CUDA)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if (WIN32)
    add_definitions(-D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -DNDEBUG)
else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -Wall -fPIC")
endif()

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH})

if (WIN32)
    set(PYBIND11_ROOT ${CMAKE_SOURCE_DIR}/ext_win64/pybind11)
    set(PYTHON_ROOT D:/Anaconda)
else()
    if (NOT PYTHON_INCLUDE_PATH)
        message(FATAL_ERROR "PYTHON_INCLUDE_PATH not set!")
    endif()
endif()

set(USE_PROFILING 0)
add_compile_definitions(USE_PROFILING=${USE_PROFILING})

set(TENSOR_RAY_CUDA_FILE TensorRay)
add_compile_definitions(TENSOR_RAY_CUDA_FILE="${TENSOR_RAY_CUDA_FILE}")

option(BUILD_SHARED_LIBS "Build shared libraries" ON)

option(CUDA_REMOVE_GLOBAL_MEMORY_SPACE_WARNING
       "Suppress the \"Advisory: Cannot tell what pointer points to, assuming global memory space\" warning nvcc makes." ON)

option(CUDA_GENERATE_DEPENDENCIES_DURING_CONFIGURE
       "Generate dependencies during configure time instead of only during build time." OFF)

# Set default CUDA architecture to 75 (Turing).
# This can be overwritten for individual targets using the CUDA_ARCHITECTURE property.
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
  set(CMAKE_CUDA_ARCHITECTURES 75-virtual)
endif()

include(CheckLanguage)
check_language(CUDA)
if(CMAKE_CUDA_COMPILER)
    message(STATUS "CUDA Compiler: ${CMAKE_CUDA_COMPILER}")
    message(STATUS "Looking for CUDA Toolkit.")
    find_package(CUDAToolkit EXACT 11.5 REQUIRED)
    message(STATUS "CUDA Compiler: ${CMAKE_CUDA_COMPILER}")
else()
    message(FATAL_ERROR "Cannot find CUDA.")
endif()

mark_as_advanced(CLEAR CUDA_64_BIT_DEVICE_CODE)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")

set(CUDA_GENERATED_OUTPUT_DIR "${CMAKE_BINARY_DIR}/lib/ptx")

# Optix
if (WIN32)
    set(OptiX_INSTALL_DIR "${CMAKE_SOURCE_DIR}/third_party/optix" CACHE PATH "Path to OptiX installed location.")
else()
    set(OptiX_INSTALL_DIR "${CMAKE_SOURCE_DIR}/third_party/optix" CACHE PATH "Path to OptiX installed location.")
endif()
find_package(OptiX 7.4.0 REQUIRED)

# TinyEXR
set(TinyEXR_DIR "${CMAKE_SOURCE_DIR}/third_party/tinyexr" CACHE PATH "Path to TinyEXR location.")

# Miniz
if (WIN32)
    set(Miniz_DIR "${CMAKE_SOURCE_DIR}/ext_win64/miniz" CACHE PATH "Path to Miniz installed location.")
else()
    set(Miniz_DIR "${CMAKE_SOURCE_DIR}/ext/miniz" CACHE PATH "Path to Miniz installed location.")
endif()

set(LibTensorRay ${CMAKE_SOURCE_DIR}/LibTensorRay)

set(LibEDX ${LibTensorRay}/EDXUtil)
set(LibTRRenderer ${LibTensorRay}/Renderer)
set(LibAlgo1 ${LibTRRenderer}/Algorithm1)
set(LibCUKD ${LibTensorRay}/cukd)
set(LibTensor ${LibTensorRay}/Tensor)
set(LibExamples ${LibTensorRay}/Examples)
set(LibTest ${LibTensorRay}/Test)

if (WIN32)
    include_directories(
        ${LibTensorRay}
        ${LibEDX}
        ${OptiX_INCLUDE}
        ${CMAKE_CURRENT_SOURCE_DIR}/cuda
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${CMAKE_BINARY_DIR}/include
        ${CMAKE_CURRENT_BINARY_DIR}
        ${CUDA_INCLUDE_DIRS}
        ${LibCUKD}
        ${Miniz_DIR}
        ${PYTHON_ROOT}/include
        ${PYBIND11_ROOT}/include
        ${TinyEXR_DIR}
    )
    link_directories(
        ${PYTHON_ROOT}/libs
    )
else()
    include_directories(
        "include/"
        "${OptiX_INCLUDE}"
        "${CMAKE_CURRENT_SOURCE_DIR}/cuda"
        ${CMAKE_CURRENT_SOURCE_DIR}
        "${CMAKE_BINARY_DIR}/include"
        ${CMAKE_CURRENT_BINARY_DIR}
        ${CUDA_INCLUDE_DIRS}
        ${LibCUKD}
        ${Miniz_DIR}
        ${PYTHON_INCLUDE_PATH}
        ${TinyEXR_DIR}
    )
endif()

# compile ptx library
add_library(ptx OBJECT ${LibTRRenderer}/ptx.cu)
set_target_properties(ptx PROPERTIES CUDA_PTX_COMPILATION ON)
set_target_properties(ptx PROPERTIES LINKER_LANGUAGE CUDA)

set(CUKD_FILES
    ${LibCUKD}/builder.h
    ${LibCUKD}/common.h
    ${LibCUKD}/cpfinder.cu
    ${LibCUKD}/cpfinder.h
    ${LibCUKD}/fcp.h
    ${LibCUKD}/helpers.h
    ${LibCUKD}/knn.h
    ${LibCUKD}/knnfinder.h
    ${LibCUKD}/knnfinder.cu
)

set(CORE_SOURCE_FILES
    ${LibTensor}/Tensor.cpp
    ${LibTensor}/Tensor.cu

    ${LibEDX}/Graphics/BaseCamera.cpp
    ${LibEDX}/Graphics/Color.cpp
    ${LibEDX}/Graphics/ObjMesh.cpp
    ${LibEDX}/Graphics/Texture.cpp

    ${LibEDX}/Math/Matrix.cpp
    ${LibEDX}/Math/FFT.cpp

    ${LibEDX}/Windows/Bitmap.cpp
    ${LibEDX}/Windows/Debug.cpp
    ${LibEDX}/Windows/stb_image.c

    ${LibTRRenderer}/BSDF.h
    ${LibTRRenderer}/BSDF.cpp
    ${LibTRRenderer}/Camera.h
    ${LibTRRenderer}/Camera.cpp
    ${LibTRRenderer}/Config.h
    ${LibTRRenderer}/Diffuse.h
    ${LibTRRenderer}/Diffuse.cpp
    ${LibTRRenderer}/Distribution.h
    ${LibTRRenderer}/Distribution.cpp
    ${LibTRRenderer}/Distribution.cu
    ${LibTRRenderer}/Edge.h
    ${LibTRRenderer}/Edge.cpp
    ${LibTRRenderer}/EXRImageUtils.h
    ${LibTRRenderer}/Integrator.h
    ${LibTRRenderer}/Integrator.cpp
    ${LibTRRenderer}/Light.h
    ${LibTRRenderer}/Light.cpp
    ${LibTRRenderer}/Microfacet.h
    ${LibTRRenderer}/Microfacet.cpp
    ${LibTRRenderer}/Optix.h
    ${LibTRRenderer}/Optix.cpp
    ${LibTRRenderer}/PathTracer.h
    ${LibTRRenderer}/PathTracer.cpp
    ${LibTRRenderer}/ParticleTracer.h
    ${LibTRRenderer}/ParticleTracer.cpp
    ${LibTRRenderer}/PathTracerDebug.h
    ${LibTRRenderer}/PathTracerDebug.cpp
    ${LibTRRenderer}/Primitive.h
    ${LibTRRenderer}/Primitive.cpp
    ${LibTRRenderer}/pybind_utils.h
    ${LibTRRenderer}/pybind_utils.cpp
    ${LibTRRenderer}/Ray.h
    ${LibTRRenderer}/Ray.cpp
    ${LibTRRenderer}/Records.h
    ${LibTRRenderer}/Records.cpp
    # ${LibTRRenderer}/RISPathTracer.h
    # ${LibTRRenderer}/RISPathTracer.cpp
    ${LibTRRenderer}/Roughconductor.h
    ${LibTRRenderer}/Roughconductor.cpp
    ${LibTRRenderer}/RoughDielectric.h
    ${LibTRRenderer}/RoughDielectric.cpp
    ${LibTRRenderer}/Scene.h
    ${LibTRRenderer}/Scene.cpp
    ${LibTRRenderer}/SceneLoader.h
    ${LibTRRenderer}/SceneLoader.cpp
    ${LibTRRenderer}/Utils.h
    ${LibTRRenderer}/Utils.cpp
    ${LibTRRenderer}/Boundary.h
    ${LibTRRenderer}/Boundary.cpp
    ${LibTRRenderer}/BoundaryDirect.cpp
    ${LibTRRenderer}/BoundaryIndirect.cpp
    ${LibTRRenderer}/BoundaryPrimary.cpp
    ${LibTRRenderer}/BoundaryPixel.cpp

    ${LibTRRenderer}/NEE.cu
    ${LibTRRenderer}/NEE.cuh

    ${LibTRRenderer}/AQ_distrb.h

    ${LibAlgo1}/PathVertex.h
    ${LibAlgo1}/Algorithm1.h
    ${LibAlgo1}/Algorithm1.cpp
    ${LibAlgo1}/BoundaryDirect2.h
    ${LibAlgo1}/BoundaryDirect2.cpp
    ${LibAlgo1}/BoundaryPixel2.h
    ${LibAlgo1}/BoundaryPixel2.cpp
    ${LibAlgo1}/BoundaryPrimary2.h
    ${LibAlgo1}/BoundaryPrimary2.cpp
    ${LibAlgo1}/Direct2.h
    ${LibAlgo1}/Direct2.cpp
    ${LibAlgo1}/PathSampler.h
    ${LibAlgo1}/PathSampler.cpp
    ${LibAlgo1}/Path2.h
    ${LibAlgo1}/Path2.cpp

    ${LibAlgo1}/ReSTIR/MaterialSpaceReSTIR.h
    ${LibAlgo1}/ReSTIR/MaterialSpaceReSTIR.cpp
    ${LibAlgo1}/ReSTIR/ReSTIRDataTypes.h
    ${LibAlgo1}/ReSTIR/ReSTIRDataTypes.cpp
    # ${LibAlgo1}/ReSTIR/ScreenSpaceReSTIR.h
    # ${LibAlgo1}/ReSTIR/ScreenSpaceReSTIR.cpp

    ${LibTest}/TestCuda.cu
    ${LibTest}/Test.cpp

    ${LibExamples}/Validations/ValidationExamples.cpp
    ${LibExamples}/Validations/CboxExample.cpp

    ${LibExamples}/InvRendering/InvRenderingExamples.cpp
    ${LibExamples}/InvRendering/BunnyShadow.cpp
    ${LibExamples}/InvRendering/BunnyTexture.cpp
    ${LibExamples}/InvRendering/TriangleInCbox.cpp
    ${LibExamples}/InvRendering/TriangleInGlass.cpp

    ${Miniz_DIR}/miniz.cpp
)

set(TENSOR_RAY_SOURCE_FILES
    TensorRay.cpp
)

set(DEBUG_SOURCE_FILES
    TensorRay_debug.cpp
    TensorVis.natvis
)

add_definitions(-D_SOURCE_DIR="${CMAKE_SOURCE_DIR}/")
if (WIN32)
    add_subdirectory(${PYBIND11_ROOT})
    # add_subdirectory(${TinyEXR_DIR})

    add_library(cukd_library STATIC ${CUKD_FILES})
    set_target_properties(cukd_library PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
    set_target_properties(cukd_library PROPERTIES LINKER_LANGUAGE CUDA)
    target_link_libraries(cukd_library CUDA::cudart
        CUDA::cublas
        CUDA::curand)

    add_library(cu_library STATIC ${CORE_SOURCE_FILES})
    set_target_properties(cu_library PROPERTIES CUDA_SEPARABLE_COMPILATION OFF)
    set_target_properties(cu_library PROPERTIES LINKER_LANGUAGE CUDA)
    target_link_libraries(cu_library cukd_library
        CUDA::cudart
        CUDA::cuda_driver
        CUDA::cublas
        CUDA::cublasLt
        CUDA::curand
        CUDA::nvToolsExt)

    pybind11_add_module(TensorRay ${TENSOR_RAY_SOURCE_FILES})
    if (CMAKE_BUILD_TYPE STREQUAL "Release")
        target_compile_options(TensorRay PRIVATE /O2 /wd4251 /MP)
    else()
        target_compile_options(TensorRay PRIVATE /MP)
    endif()
    target_link_libraries(TensorRay PRIVATE cu_library)

    add_executable(Debug ${DEBUG_SOURCE_FILES})
    if (CMAKE_BUILD_TYPE STREQUAL "Release")
        target_compile_options(Debug PRIVATE /O2 /wd4251 /MP)
    else()
        target_compile_options(Debug PRIVATE /MP)
    endif()

    target_link_libraries(Debug PRIVATE cu_library)
else()
    add_library(TensorRay MODULE ${TENSOR_RAY_CUDA_FILE})
endif()


# target build
target_compile_definitions(cu_library PRIVATE PTX_PATH="$<TARGET_OBJECTS:ptx>")


set_target_properties(TensorRay PROPERTIES SKIP_BUILD_RPATH FALSE)
set_target_properties(TensorRay PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
set_property(TARGET TensorRay PROPERTY CXX_STANDARD 17)
set_target_properties(TensorRay PROPERTIES PREFIX "")

set_target_properties(Debug PROPERTIES SKIP_BUILD_RPATH FALSE)
set_target_properties(Debug PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
set_property(TARGET Debug PROPERTY CXX_STANDARD 17)
set_target_properties(Debug PROPERTIES PREFIX "")
