# Makefile for MMORF

MMORF_BUILD ?= GPU
PROJNAME     = mmorf
BUILDBASEDIR = build
BUILDDIR     = ${BUILDBASEDIR}/${MMORF_BUILD}
TESTDIR      = test
TESTBUILDDIR = test/${MMORF_BUILD}
INCDIR       = inc
SRCDIR       = src
COVDIR       = coverage

# As we are using FSL classes, this line is needed
include ${FSLCONFDIR}/default.mk

# Generate version number from the
# state of the git repository
MMORF_VERSION ?= $(shell git describe --tags --always --dirty)

# The MMORF binary is named either mmorf_cpu or
# mmorf_cuda. The "mmorf" script will call
# one or the other depending on whether a GPU is
# available.
ifeq (${MMORF_BUILD}, GPU)
  MMORF_EXE = mmorf_cuda
  SCRIPTS   =
else
  MMORF_EXE = mmorf_cpu
  SCRIPTS   = mmorf
endif

XFILES = ${BUILDDIR}/${MMORF_EXE}

# Danny Ruijters' Cubic Interpolation (CI) directory
CI_DIR     = CubicInterpolationCUDA
CI_INC_DIR = CubicInterpolationCUDA/code
CI_LIB     = ${BUILDDIR}/libCubicInterp.a

# We set the following macros for use in the MMORF source:
#  - MMORF_VERSION   - version string
#  - MMORF_BUILD     - "CPU" or "GPU"
#  - MMORF_BUILD_GPU - defined for GPU builds
#  - MMORF_BUILD_CPU - defined for CPU builds
USRCPPFLAGS  = -DARMA_DONT_PRINT_ERRORS             \
               -DEXPOSE_TREACHEROUS                 \
               -DMMORF_VERSION=\"${MMORF_VERSION}\" \
               -DMMORF_BUILD_${MMORF_BUILD}         \
               -DMMORF_BUILD=\"${MMORF_BUILD}\"
USRINCFLAGS  = -I$(INCDIR) -I${CI_INC_DIR}
USRCXXFLAGS  = -fopenmp
USRNVCCFLAGS = --relocatable-device-code true
TESTCPPFLAGS =  -DMMORF_TEST_DIRECTORY=\"$(realpath ${TESTDIR})\"
LIBS         = -lfsl-newmeshclass -lfsl-basisfield -lfsl-newimage -lfsl-miscmaths \
               -lfsl-NewNifti -lfsl-utils -lfsl-znz -lgomp
CUDALIBS     = -lcurand

# Add coverage flags if testing
# Note that tHis causes code to run substantially slower!
ifdef TEST_COVERAGE
  USRCXXFLAGS += -fprofile-arcs -fprofile-abs-path -ftest-coverage
endif

ifeq (${MMORF_BUILD}, GPU)
  OBJS = CostFxnKernelsCoreGPU.o \
         TextureHandle.o
endif

OBJS += BiasFieldBSpline.o \
        Config.o \
        CostFxnBendingEnergy.o \
        CostFxnBendingEnergyCore${MMORF_BUILD}.o \
        CostFxnCompoundVarianceScaled.o \
        CostFxnKernels.o \
        CostFxnKernels${MMORF_BUILD}.o \
        CostFxnLogJacobianSingularValues.o \
        CostFxnLogJacobianSingularValuesCore${MMORF_BUILD}.o \
        CostFxnLogJacobianSingularValuesKernels${MMORF_BUILD}.o \
        CostFxnPointCloudWarpField.o \
        CostFxnSSDBiasFieldMaskedExcluded.o \
        CostFxnSSDWarpFieldSymmetricMaskedExcluded.o \
        CostFxnTensorL2WarpFieldSymmetricMaskedExcluded.o \
        CostFxnTensorL2WarpFieldSymmetricMaskedExcludedCore${MMORF_BUILD}.o \
        CostFxnTensorL2WarpFieldSymmetricMaskedExcludedKernels${MMORF_BUILD}.o \
        IntensityMapperPolynomial.o \
        LinearSolverCG.o \
        MMORFOptions.o \
        OptimiserLevenberg.o \
        OptimiserLevenbergMarquardt.o \
        OptimiserScaledConjugateGradient.o \
        RegistrationCoordinatorMultimodal.o \
        SparseDiagonalMatrixTiled.o \
        SparseDiagonalMatrixTiled${MMORF_BUILD}.o \
        VolumeBSpline.o \
        VolumeBSplineSampler${MMORF_BUILD}.o \
        VolumeTensor.o \
        WarpFieldBSpline.o \
        WarpFieldBSplineUtils${MMORF_BUILD}.o \
        WarpUtils.o

OBJS := $(OBJS:%.o=${BUILDDIR}/%.o)

TESTOBJS  = catch_amalgamated.o \
            mmorf_test_utils.o \
            test_LinearSolver.o \
            test_MMORFOptions.o \
            test_VolumeBSpline.o \
            test_SparseDiagonalMatrixTiled${MMORF_BUILD}.o \
            test_WarpFieldBSpline.o \
            test_CostFxnTensorL2WarpFieldSymmetricMaskedExcluded.o \
            test_CostFxnBendingEnergy.o \
            test_CostFxnKernels.o

TESTOBJS := $(TESTOBJS:%.o=${TESTBUILDDIR}/%.o)

all: $(XFILES)

clean:
	make -C ${CI_DIR} clean
	rm -rf ${BUILDBASEDIR}   \
           .deps             \
           ${TESTDIR}/*.gcno \
           ${TESTDIR}/*.gcda \
           test/GPU          \
           test/CPU          \
           ${COVDIR}

# CubicInterpolationCUDA compiled
# as a static library
${CI_LIB}:
	@mkdir -p ${BUILDDIR}
	${MAKE} -C ${CI_DIR}
	cp ${CI_DIR}/libCubicInterp.a $@

# mmorf executable
ifeq (${MMORF_BUILD}, GPU)
${BUILDDIR}/${MMORF_EXE}: ${BUILDDIR}/mmorf.o ${OBJS} ${CI_LIB}
	${NVCC} ${NVCCFLAGS} -o $@ $^ ${NVCCLDFLAGS}
else
${BUILDDIR}/${MMORF_EXE}: ${BUILDDIR}/mmorf.o ${OBJS}
	${CXX} ${CXXFLAGS} -o $@ $^ ${LDFLAGS}
endif

# Test CUDA object files
${TESTBUILDDIR}/%.o: $(TESTDIR)/%.cu
	@mkdir -p ${TESTBUILDDIR}
	${NVCC} ${NVCCFLAGS} ${TESTCPPFLAGS} -c -o $@ $<

# Test C++ object files
${TESTBUILDDIR}/%.o: $(TESTDIR)/%.cpp
	@mkdir -p ${TESTBUILDDIR}
	$(CXX) $(CXXFLAGS) ${TESTCPPFLAGS} -c -o $@ $<

# Test executable
.PHONY: tests
tests: test/run_tests_${MMORF_BUILD}
ifeq (${MMORF_BUILD}, GPU)
test/run_tests_${MMORF_BUILD}: ${OBJS} ${TESTOBJS} ${CI_LIB}
	${NVCC} ${NVCCFLAGS} -o $@ $^ ${NVCCLDFLAGS}
else
test/run_tests_${MMORF_BUILD}: ${OBJS} ${TESTOBJS}
	${CXX} ${CXXFLAGS} -o $@ $^ ${LDFLAGS}
endif

# After running tests, run "make coverage"
# to generate a coverage report. You need
# to set GCOV to the gcov executable, you
# you need to have lcov installed, and you
# need to have compiled the tests with the
# TEST_COVERAGE variable set.
.PHONY: coverage
coverage:
	@if [ -z ${TEST_COVERAGE} ]; then echo "TEST_COVERAGE not set"; exit 1; fi
	@if [ -z ${GCOV} ];          then echo "GCOV not set";          exit 1; fi
	@if ! command -v geninfo;    then echo "lcov not found";        exit 1; fi
	@rm -rf   ${COVDIR}
	@mkdir -p ${COVDIR}
	geninfo --gcov-tool ${GCOV}           \
	  ${BUILDDIR}                         \
	  --include  '$(abspath ${SRCDIR})/*' \
	  -o ${COVDIR}/geninfo.txt
	genhtml ${COVDIR}/geninfo.txt -o ${COVDIR} --legend --dark-mode
	@echo "Open ${COVDIR}/index.html in your web browser"


# Print a varable name with "make print-VARIABLE"
print-%  : ; @echo $* = $($*)
