cmake_minimum_required(VERSION 3.6)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_C_STANDARD 99)
project(Granite LANGUAGES CXX C)

if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
    set(GRANITE_CXX_FLAGS -Wshadow -Wall -Wextra -Wno-comment -Wno-missing-field-initializers -Wno-empty-body)
    if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
        set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} -Wno-backslash-newline-escape)
    endif()
    if (NOT (${CMAKE_BUILD_TYPE} MATCHES "Release"))
        message("Enabling frame pointer for profiling/debug.")
        set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} -fno-omit-frame-pointer)
    endif()
    if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
        message("Enabling SSE3 support.")
        set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} -msse3)
    endif()
elseif (MSVC)
    set(GRANITE_CXX_FLAGS /D_CRT_SECURE_NO_WARNINGS /wd4267 /wd4244 /wd4309 /wd4005 /MP)
endif()

include(GNUInstallDirs)

option(GRANITE_SHARED "Build Granite as a shared library." OFF)
option(GRANITE_ISPC_TEXTURE_COMPRESSION "Enable ISPC texture compression" OFF)
option(GRANITE_ASTC_ENCODER_COMPRESSION "Enable astc-encoder texture compression" OFF)
option(GRANITE_TOOLS "Build Granite tools." ON)
option(GRANITE_KHR_DISPLAY_ACQUIRE_XLIB "Try to acquire Xlib display when using VK_KHR_display." OFF)
option(GRANITE_ANDROID_APK_FILESYSTEM "Use APK file system for assets and builtin files." ON)
option(GRANITE_ANDROID_SWAPPY "Use swappy on Android." OFF)
option(GRANITE_SHADER_COMPILER_OPTIMIZE "Optimize SPIR-V." ON)
option(GRANITE_VULKAN_SYSTEM_HANDLES "Add system handle support to Vulkan backend." ON)
option(GRANITE_RENDERER "Add higher level rendering functionality." ON)
option(GRANITE_VULKAN_SPIRV_CROSS "Add reflection support to Vulkan backend." ON)
option(GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER "Enable Vulkan GLSL runtime compiler." ON)
option(GRANITE_VULKAN_FOSSILIZE "Enable support for Fossilize." ON)
option(GRANITE_VULKAN_PROFILES "Enable profiles support." OFF)
option(GRANITE_VULKAN_DXGI_INTEROP "Enable DXGI interop when running on Windows." ON)
option(GRANITE_VULKAN_POST_MORTEM "Add extra support for port-mortem debug." OFF)
option(GRANITE_AUDIO "Enable Audio support." OFF)
option(GRANITE_PLATFORM "Granite Platform" "SDL")
option(GRANITE_HIDDEN "Declare symbols as hidden by default. Useful if you build Granite as a static library and you link to it in your shared library." OFF)
option(GRANITE_SANITIZE_ADDRESS "Sanitize address" OFF)
option(GRANITE_SANITIZE_THREADS "Sanitize threads" OFF)
option(GRANITE_SANITIZE_MEMORY "Sanitize memory" OFF)
option(GRANITE_TARGET_NATIVE "Target native arch (-march=native)" OFF)
option(GRANITE_BULLET "Enable Bullet support." OFF)
option(GRANITE_RENDERDOC_CAPTURE "Enable support for RenderDoc capture from API." ON)
option(GRANITE_INSTALL_TARGETS "Add install targets." ON)
option(GRANITE_INSTALL_EXE_TARGETS "Add executable install targets." OFF)
option(GRANITE_FFMPEG "Enable FFmpeg." OFF)
option(GRANITE_FFMPEG_VULKAN "Enable experimental Vulkan HW decode support in FFmpeg." OFF)
option(GRANITE_FAST_MATH "Enable fast math." OFF)
option(GRANITE_SHIPPING "Disable code paths not related to development." OFF)
option(GRANITE_SYSTEM_SDL "Use system SDL3 instead of vendored submodule." OFF)

if (GRANITE_FAST_MATH)
    message("Enabling fast math.")
    if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
        set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} -ffast-math)
    elseif (MSVC)
        set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} /fp:fast)
    endif()
else()
    message("Enabling precise math.")
    if (MSVC)
        set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} /fp:precise)
    endif()
endif()

if (NOT GRANITE_PLATFORM)
    set(GRANITE_PLATFORM "SDL")
endif()

if (GRANITE_HIDDEN)
    if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
    endif()
elseif(${GRANITE_PLATFORM} MATCHES "libretro")
    if (NOT GRANITE_SHARED)
        if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
            set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
        endif()
    endif()
endif()

if (GRANITE_TARGET_NATIVE)
    if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native")
    endif()
endif()

set(GRANITE_LINK_FLAGS)

if (${CMAKE_VERSION} GREATER "3.13")
	# Executable targets are not stripped in release mode on Android, do it explicitly.
    add_link_options($<$<AND:$<PLATFORM_ID:Android>,$<CONFIG:RELEASE>>:-s>)
endif()

if (GRANITE_SANITIZE_ADDRESS)
    set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} -fsanitize=address)
    set(GRANITE_LINK_FLAGS "${GRANITE_LINK_FLAGS} -fsanitize=address")
endif()

if (GRANITE_SANITIZE_THREADS)
    set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} -fsanitize=thread)
    set(GRANITE_LINK_FLAGS "${GRANITE_LINK_FLAGS} -fsanitize=thread")
endif()

if (GRANITE_SANITIZE_MEMORY)
    set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} -fsanitize=memory)
    set(GRANITE_LINK_FLAGS "${GRANITE_LINK_FLAGS} -fsanitize=memory")
endif()

if (ANDROID)
    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()

if (GRANITE_SHARED)
    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()

if (GRANITE_SHARED)
    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
else()
    if (${GRANITE_PLATFORM} MATCHES "libretro")
        set(CMAKE_POSITION_INDEPENDENT_CODE ON)
    elseif (GRANITE_POSITION_INDEPENDENT)
        set(CMAKE_POSITION_INDEPENDENT_CODE ON)
    elseif (ANDROID)
        set(CMAKE_POSITION_INDEPENDENT_CODE ON)
    else()
        set(CMAKE_POSITION_INDEPENDENT_CODE OFF)
    endif()
endif()

set(GRANITE_VERSION_MAJOR 0)
set(GRANITE_VERSION_MINOR 0)
set(GRANITE_VERSION_PATCH 0)

# Make sure .dlls are placed next to .exe, since there is no RPATH.
if (WIN32)
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
endif()

function(granite_install_executable NAME)
    install(TARGETS ${NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
    if (NOT WIN32)
        set_target_properties(${NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN/../lib")
    endif()
endfunction()

macro(granite_setup_default_link_libraries NAME)
    if (NOT WIN32)
        target_link_libraries(${NAME} PRIVATE -pthread dl m)
        find_library(LIBRT_LIBRARY rt)
        if (EXISTS ${LIBRT_LIBRARY})
            target_link_libraries(${NAME} PUBLIC rt)
        endif()
    endif()
    if (WIN32 AND CMAKE_COMPILER_IS_GNUCXX)
        target_link_libraries(${NAME} PRIVATE -static gcc stdc++ winpthread)
    endif()
endmacro()

function(add_granite_third_party_lib NAME)
    if (GRANITE_SHARED)
        add_library(${NAME} SHARED ${ARGN})

        if (GRANITE_INSTALL_TARGETS)
            if (WIN32)
                install(TARGETS ${NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
            else()
                install(TARGETS ${NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR})
                set_target_properties(${NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN/")
                set_target_properties(${NAME} PROPERTIES
                    VERSION ${GRANITE_VERSION_MAJOR}.${GRANITE_VERSION_MINOR}.${GRANITE_VERSION_PATCH}
                    SOVERSION ${GRANITE_VERSION_MAJOR})
            endif()
        endif()

        granite_setup_default_link_libraries(${NAME})

        if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
            set_target_properties(${NAME} PROPERTIES LINK_FLAGS "-Wl,--no-undefined")
        endif()
        if (MSVC)
            set_target_properties(${NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
        endif()
    else()
        add_library(${NAME} STATIC ${ARGN})
        if (MSVC)
            set_target_properties(${NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS OFF)
        endif()
    endif()
endfunction()

function(add_granite_internal_lib NAME)
    add_granite_third_party_lib(${NAME} ${ARGN})
    target_compile_options(${NAME} PRIVATE ${GRANITE_CXX_FLAGS})
    if (GRANITE_SHARED AND MSVC)
        set_target_properties(${NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
    endif()
endfunction()

function(add_granite_internal_static_lib NAME)
    add_library(${NAME} STATIC ${ARGN})
    target_compile_options(${NAME} PRIVATE ${GRANITE_CXX_FLAGS})
endfunction()

add_subdirectory(third_party)
add_subdirectory(util)
add_subdirectory(path)
add_subdirectory(math)
add_subdirectory(threading)
add_subdirectory(compiler)
add_subdirectory(filesystem)
add_subdirectory(vulkan)
add_subdirectory(ecs)
add_subdirectory(event)
if (GRANITE_VULKAN_SYSTEM_HANDLES AND GRANITE_RENDERER)
    add_subdirectory(renderer)
    add_subdirectory(ui)
    add_subdirectory(scene-export)
endif()

if (GRANITE_FFMPEG)
    add_subdirectory(video)
endif()

add_subdirectory(application)

if (GRANITE_AUDIO)
    add_subdirectory(audio)
endif()
if (GRANITE_BULLET)
    add_subdirectory(physics)
endif()

add_library(granite-base INTERFACE)
target_link_libraries(granite-base INTERFACE granite-util granite-path granite-application granite-vulkan)
if (TARGET granite-renderer)
    target_link_libraries(granite-base INTERFACE granite-renderer granite-ui)
endif()

function(add_granite_executable NAME)
    if (ANDROID)
        add_library(${NAME} SHARED ${ARGN})
        target_link_libraries(${NAME} PRIVATE log android)
    elseif(${GRANITE_PLATFORM} MATCHES "libretro")
        add_library(${NAME} SHARED ${ARGN})
        if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
            set_target_properties(${NAME} PROPERTIES LINK_FLAGS "-Wl,--no-undefined")
        endif()
    else()
        add_executable(${NAME} ${ARGN})
    endif()
    # Need this for some reason on OSX.
    if (APPLE)
        set_target_properties(${NAME} PROPERTIES LINK_FLAGS "-Wl,-all_load")
    endif()
    target_link_libraries(${NAME} PRIVATE granite-base granite-application-global-init)
    target_compile_options(${NAME} PRIVATE ${GRANITE_CXX_FLAGS})
    granite_setup_default_link_libraries(${NAME})
    if (GRANITE_SANITIZE_ADDRESS OR GRANITE_SANITIZE_THREADS OR GRANITE_SANITIZE_MEMORY)
        set_target_properties(${NAME} PROPERTIES LINK_FLAGS "${GRANITE_LINK_FLAGS}")
    endif()
endfunction()

function(add_granite_application TARGET_NAME)
    if (WIN32 AND (NOT ${GRANITE_PLATFORM} MATCHES "libretro"))
        add_granite_executable(${TARGET_NAME} WIN32 ${ARGN})
    else()
        add_granite_executable(${TARGET_NAME} ${ARGN})
    endif()
    if (ANDROID)
        target_link_libraries(${TARGET_NAME} PRIVATE granite-base granite-platform)
    elseif (${GRANITE_PLATFORM} MATCHES "libretro")
        target_link_libraries(${TARGET_NAME} PRIVATE granite-base granite-platform)
    else()
        target_link_libraries(${TARGET_NAME} PRIVATE granite-application-entry)
    endif()
    granite_setup_default_link_libraries(${TARGET_NAME})
    if (GRANITE_INSTALL_EXE_TARGETS)
        granite_install_executable(${TARGET_NAME})
    endif()
endfunction()

function(add_granite_headless_application TARGET_NAME)
    add_executable(${TARGET_NAME} ${ARGN})

    target_compile_options(${TARGET_NAME} PRIVATE ${GRANITE_CXX_FLAGS})
    if (GRANITE_SANITIZE_ADDRESS OR GRANITE_SANITIZE_THREADS OR GRANITE_SANITIZE_MEMORY)
        set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "${GRANITE_LINK_FLAGS}")
    endif()
    if (ANDROID)
        target_link_libraries(${TARGET_NAME} PRIVATE log android)
    endif()
    target_link_libraries(${TARGET_NAME} PRIVATE granite-base granite-application-entry-headless)
    granite_setup_default_link_libraries(${TARGET_NAME})
    if (GRANITE_INSTALL_EXE_TARGETS)
        granite_install_executable(${TARGET_NAME})
    endif()
endfunction()

function(add_granite_offline_tool NAME)
    add_executable(${NAME} ${ARGN})
    target_compile_options(${NAME} PRIVATE ${GRANITE_CXX_FLAGS})

    # Need this for some reason on OSX.
    if (APPLE)
        set_target_properties(${NAME} PROPERTIES LINK_FLAGS "-Wl,-all_load")
    endif()
    target_link_libraries(${NAME} PRIVATE granite-base granite-application-global-init)
    target_compile_options(${NAME} PRIVATE ${GRANITE_CXX_FLAGS})
    if (GRANITE_SANITIZE_ADDRESS OR GRANITE_SANITIZE_THREADS OR GRANITE_SANITIZE_MEMORY)
        set_target_properties(${NAME} PROPERTIES LINK_FLAGS "${GRANITE_LINK_FLAGS}")
    endif()
    granite_setup_default_link_libraries(${NAME})
    if (GRANITE_INSTALL_EXE_TARGETS)
        granite_install_executable(${NAME})
    endif()
endfunction()

if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}")
    set(GRANITE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
else()
    set(GRANITE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE)
    set(GRANITE_CXX_FLAGS ${GRANITE_CXX_FLAGS} PARENT_SCOPE)
    set(GRANITE_LINK_FLAGS ${GRANITE_LINK_FLAGS} PARENT_SCOPE)
endif()

function(add_blob_archive_target TARGET_NAME BLOB_NAME)
    find_package(Python3 REQUIRED)
    set_source_files_properties(
            ${CMAKE_BINARY_DIR}/${BLOB_NAME}.h
            ${CMAKE_BINARY_DIR}/${BLOB_NAME}.c
            PROPERTIES GENERATED ON)
    add_library(${TARGET_NAME} STATIC
            ${CMAKE_BINARY_DIR}/${BLOB_NAME}.h
            ${CMAKE_BINARY_DIR}/${BLOB_NAME}.c)
    target_include_directories(${TARGET_NAME} PUBLIC ${CMAKE_BINARY_DIR})
    # ... CMake, y u gotta be like this.
    set(_arg_list)
    set(_arg_counter 0)
    foreach(_arg IN LISTS ARGN)
        math(EXPR _arg_counter_mod "${_arg_counter} % 2")
        if (NOT ${_arg_counter_mod})
            list(APPEND _arg_list --input)
        endif()
        list(APPEND _arg_list ${_arg})
        math(EXPR _arg_counter "${_arg_counter} + 1")
    endforeach()
    add_custom_command(OUTPUT
        ${CMAKE_BINARY_DIR}/${BLOB_NAME}.h
        ${CMAKE_BINARY_DIR}/${BLOB_NAME}.c
        COMMAND ${Python3_EXECUTABLE}
            ${GRANITE_SOURCE_DIR}/tools/blobify.py
            --output ${CMAKE_BINARY_DIR}/${BLOB_NAME}.blob
            ${_arg_list}
        COMMAND ${Python3_EXECUTABLE}
            ${GRANITE_SOURCE_DIR}/tools/bin_to_text.py
            --output ${CMAKE_BINARY_DIR}/${BLOB_NAME}
            --blob-name ${BLOB_NAME}
            ${CMAKE_BINARY_DIR}/${BLOB_NAME}.blob
        BYPRODUCTS
            ${CMAKE_BINARY_DIR}/${BLOB_NAME}.blob)
endfunction()

if (TARGET granite-renderer)
    add_subdirectory(tests)
    add_subdirectory(viewer)
    if (GRANITE_TOOLS)
        add_subdirectory(tools)
        add_subdirectory(renderer/fft/test)
    endif()
endif()

if (GRANITE_TOOLS AND GRANITE_VULKAN_SPIRV_CROSS AND GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER)
    add_subdirectory(slangmosh)
endif()

