cmake_minimum_required(VERSION 3.21)
project(CR3U LANGUAGES C CXX)
if(WIN32)
    enable_language(RC) # (the .exe's icon: cmake/app-icon.cmake)
endif()
if(APPLE)
    enable_language(OBJCXX) # (MetalFX's upscaler: gfx/vk_metalfx.mm)
endif()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(THIRD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party)

# Squirrel 2.2.4 (unmodified; zlib licence): the script machine the game embeds. Built with 32-bit
# SQInteger (no _SQ64) on every host - the compiled scripts store their counts and integers in 4
# bytes, and Squirrel 2's loader reads them as SQInteger
file(GLOB SQUIRREL_SOURCES ${THIRD_PARTY_DIR}/squirrel2/squirrel/*.cpp ${THIRD_PARTY_DIR}/squirrel2/sqstdlib/*.cpp)
add_library(squirrel2 STATIC ${SQUIRREL_SOURCES})
target_include_directories(squirrel2 PUBLIC ${THIRD_PARTY_DIR}/squirrel2/include)
if(MINGW)
    target_include_directories(squirrel2 BEFORE PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/script/mingw) # (64-bit SQInteger)
endif()
target_include_directories(squirrel2 PRIVATE ${THIRD_PARTY_DIR}/squirrel2/squirrel)
target_compile_options(squirrel2 PRIVATE $<IF:$<CXX_COMPILER_ID:MSVC>,/w,-w -fno-strict-aliasing>
    $<IF:$<CXX_COMPILER_ID:MSVC>,/FI${CMAKE_CURRENT_SOURCE_DIR}/src/script/squirrel_prefix.h,-include ${CMAKE_CURRENT_SOURCE_DIR}/src/script/squirrel_prefix.h>)

# Core library: everything that is not an app entry point, so tools can reuse it
add_library(cr3core STATIC
    src/assets/mvgl.cpp
    src/assets/mbe.cpp
    src/assets/bson.cpp
    src/db/cr3db.cpp
    src/assets/image.cpp
    src/assets/geom.cpp
    src/assets/smooth.cpp
    src/assets/skeleton.cpp
    src/assets/maptable.cpp
    src/assets/release.cpp
    src/assets/hd_texture.cpp
    src/assets/gltf_figure.cpp
    src/assets/addons.cpp
    src/assets/text.cpp
    src/assets/card_art.cpp
    src/assets/card_art_text.cpp
    src/assets/language.cpp
    src/assets/spark.cpp
    src/assets/movie.cpp
    src/field/fieldmap.cpp
    src/field/cr3field.cpp
    src/field/collision.cpp
    src/field/character.cpp
    src/field/gimmicks.cpp
    src/field/fieldscript.cpp
    src/event/scene.cpp
    src/event/runtime.cpp
    src/gfx/material.cpp
    src/gfx/text.cpp
    src/script/vm.cpp
    src/script/gamestate.cpp
    src/script/gamesave.cpp
    src/battle/battledata.cpp
    src/battle/rules.cpp
    src/battle/cr3battle.cpp
    src/battle/cr3rules.cpp
    src/battle/motion.cpp
    src/battle/camera.cpp
    src/battle/script.cpp
    src/battle/command.cpp
    src/battle/special.cpp
    src/battle/glass.cpp
    src/field/treasure.cpp
    src/audio/sounddata.cpp
    src/audio/streams.cpp
    src/audio/cr3sound.cpp
    third_party/stb/stb_vorbis.c
    third_party/stb/stb_image.c
    third_party/gltf_libs.c
)
# The iPad's music (AAC in CAF): FAAD2 - the system's when pkg-config finds it, else the copy in
# third_party/faad2 (FAAD2 2.11.1's libfaad, CR1:U's). CR3_BUNDLED_FAAD2=ON forces the bundled one.
option(CR3_BUNDLED_FAAD2 "Build the bundled FAAD2 even if the system has one" OFF)
if(NOT CR3_BUNDLED_FAAD2)
    find_package(PkgConfig QUIET)
    if(PkgConfig_FOUND)
        pkg_check_modules(FAAD2 QUIET IMPORTED_TARGET faad2)
    endif()
endif()
if(FAAD2_FOUND)
    target_link_libraries(cr3core PUBLIC PkgConfig::FAAD2)
    target_compile_definitions(cr3core PUBLIC CR3_HAVE_FAAD2=1)
    message(STATUS "iPad music: FAAD2 ${FAAD2_VERSION} (system)")
elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/faad2/libfaad/decoder.c)
    file(GLOB CR3_FAAD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/third_party/faad2/libfaad/*.c)
    add_library(cr3_faad2 STATIC ${CR3_FAAD_SOURCES})
    target_include_directories(cr3_faad2 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/faad2/include
                               PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/faad2/libfaad)
    # (FAAD2's own CMakeLists' fixed configuration: floating point, DRC, no DRM)
    target_compile_definitions(cr3_faad2 PRIVATE APPLY_DRC HAVE_INTTYPES_H=1 HAVE_MEMCPY=1 HAVE_STRING_H=1
                               STDC_HEADERS=1 PACKAGE_VERSION="2.11.1")
    if(NOT MSVC)
        target_compile_definitions(cr3_faad2 PRIVATE HAVE_STRINGS_H=1 HAVE_SYS_STAT_H=1 HAVE_SYS_TYPES_H=1)
    endif()
    include(CheckSymbolExists)
    if(NOT MSVC)
        set(CMAKE_REQUIRED_LIBRARIES m)
    endif()
    check_symbol_exists(lrintf "math.h" CR3_HAVE_LRINTF)
    unset(CMAKE_REQUIRED_LIBRARIES)
    if(CR3_HAVE_LRINTF)
        target_compile_definitions(cr3_faad2 PRIVATE HAVE_LRINTF=1)
    endif()
    if(MSVC)
        target_compile_options(cr3_faad2 PRIVATE /w)
    else()
        target_compile_options(cr3_faad2 PRIVATE -w)
        find_library(CR3_MATH_LIBRARY m)
        if(CR3_MATH_LIBRARY)
            target_link_libraries(cr3_faad2 PUBLIC ${CR3_MATH_LIBRARY})
        endif()
    endif()
    set_target_properties(cr3_faad2 PROPERTIES POSITION_INDEPENDENT_CODE ON)
    target_link_libraries(cr3core PUBLIC cr3_faad2)
    target_compile_definitions(cr3core PUBLIC CR3_HAVE_FAAD2=1)
    message(STATUS "iPad music: FAAD2 2.11.1 (bundled)")
else()
    message(STATUS "iPad music: no FAAD2, not played")
endif()
# The movies (H.264 in MP4: assets/movie.hpp): OpenH264's decoder - the system's when pkg-config
# finds it, else the copy in third_party/openh264 (OpenH264 2.6.0's codec/api, common and decoder,
# its C++ alone - no assembly). CR3_BUNDLED_OPENH264=ON forces the bundled one.
option(CR3_BUNDLED_OPENH264 "Build the bundled OpenH264 even if the system has one" OFF)
if(NOT CR3_BUNDLED_OPENH264)
    find_package(PkgConfig QUIET)
    if(PkgConfig_FOUND)
        pkg_check_modules(OPENH264 QUIET IMPORTED_TARGET openh264)
    endif()
endif()
set(CR3_OH264 ${CMAKE_CURRENT_SOURCE_DIR}/third_party/openh264/codec)
if(OPENH264_FOUND)
    target_link_libraries(cr3core PUBLIC PkgConfig::OPENH264)
    target_compile_definitions(cr3core PUBLIC CR3_HAVE_OPENH264=1)
    message(STATUS "Movies: OpenH264 ${OPENH264_VERSION} (system)")
elseif(EXISTS ${CR3_OH264}/decoder/plus/src/welsDecoderExt.cpp)
    file(GLOB CR3_OH264_SOURCES ${CR3_OH264}/common/src/*.cpp ${CR3_OH264}/decoder/core/src/*.cpp
         ${CR3_OH264}/decoder/plus/src/*.cpp)
    add_library(cr3_openh264 STATIC ${CR3_OH264_SOURCES})
    target_include_directories(cr3_openh264 PUBLIC ${CR3_OH264}/api
                               PRIVATE ${CR3_OH264}/api/wels ${CR3_OH264}/common/inc ${CR3_OH264}/decoder/core/inc
                                       ${CR3_OH264}/decoder/plus/inc)
    if(MSVC)
        target_compile_options(cr3_openh264 PRIVATE /w)
    else()
        target_compile_options(cr3_openh264 PRIVATE -w)
    endif()
    find_package(Threads)
    if(Threads_FOUND)
        target_link_libraries(cr3_openh264 PUBLIC Threads::Threads)
    endif()
    set_target_properties(cr3_openh264 PROPERTIES POSITION_INDEPENDENT_CODE ON)
    target_link_libraries(cr3core PUBLIC cr3_openh264)
    target_compile_definitions(cr3core PUBLIC CR3_HAVE_OPENH264=1)
    message(STATUS "Movies: OpenH264 2.6.0 (bundled)")
else()
    message(STATUS "Movies: no OpenH264, not played")
endif()
# The Vita's music (ATRAC9 in SXD): LibAtrac9's C decoder (CR1:U's copy), built here
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/libatrac9/src/libatrac9.c)
    file(GLOB CR3_ATRAC9_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/third_party/libatrac9/src/*.c)
    add_library(cr3_atrac9 STATIC ${CR3_ATRAC9_SOURCES})
    target_include_directories(cr3_atrac9 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/libatrac9/src)
    # (its header marks the functions dllimport under MSVC unless COMPILING_DLL: a static library here)
    target_compile_definitions(cr3_atrac9 PUBLIC COMPILING_DLL)
    if(MSVC)
        target_compile_options(cr3_atrac9 PRIVATE /w)
    else()
        target_compile_options(cr3_atrac9 PRIVATE -w)
        find_library(CR3_ATRAC9_MATH m)
        if(CR3_ATRAC9_MATH)
            target_link_libraries(cr3_atrac9 PUBLIC ${CR3_ATRAC9_MATH})
        endif()
    endif()
    set_target_properties(cr3_atrac9 PROPERTIES POSITION_INDEPENDENT_CODE ON)
    target_link_libraries(cr3core PUBLIC cr3_atrac9)
    target_compile_definitions(cr3core PUBLIC CR3_HAVE_ATRAC9=1)
    message(STATUS "Vita music: LibAtrac9 (bundled)")
endif()
# stb_vorbis (public domain, vendored - CR1:U's copy): the Android release's music (Ogg Vorbis)
set_source_files_properties(third_party/stb/stb_vorbis.c PROPERTIES COMPILE_OPTIONS "$<IF:$<C_COMPILER_ID:MSVC>,/w,-w>")
# stb_image (public domain, vendored - 2.30, JPEG and PNG only): a gene card's page's pictures
set_source_files_properties(third_party/stb/stb_image.c PROPERTIES COMPILE_OPTIONS "$<IF:$<C_COMPILER_ID:MSVC>,/w,-w>")
# cgltf 1.13 (MIT) and stb_image_write 1.16 (public domain / MIT), vendored: the model bridge's glTF
# reading and the exports' PNGs (assets/gltf_figure.cpp)
set_source_files_properties(third_party/gltf_libs.c PROPERTIES COMPILE_OPTIONS "$<IF:$<C_COMPILER_ID:MSVC>,/w,-w>")
target_include_directories(cr3core SYSTEM PRIVATE third_party)
target_include_directories(cr3core PRIVATE third_party/stb)
target_include_directories(cr3core PUBLIC src)
target_include_directories(cr3core PRIVATE third_party/hevag) # (the HE-VAG filter table: vgmstream's, ISC)
target_link_libraries(cr3core PUBLIC squirrel2)
# The Android release's APK (its library: the maps' table; its fonts): zlib (the system's) inflates
# them; without it, only an unpacked APK's
# zlib and the like: the system's, else (Windows, macOS, Android - CR_FETCH_DEPS) built here from
# their sources, static
option(CR_FETCH_DEPS "Build zlib (and FreeType) from source where the system has none" ON)
find_package(ZLIB QUIET)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
    set(CR_FETCH_EXCLUDE EXCLUDE_FROM_ALL)
endif()
if(NOT ZLIB_FOUND AND CR_FETCH_DEPS)
    include(FetchContent)
    set(CMAKE_POLICY_VERSION_MINIMUM 3.5) # (zlib's asks for CMake 2.4, which CMake 4 refuses)
    FetchContent_Declare(zlib GIT_REPOSITORY https://github.com/madler/zlib.git GIT_TAG v1.3.1 GIT_SHALLOW TRUE ${CR_FETCH_EXCLUDE})
    # The excluded dependency's example is not built or part of the port's tests.
    set(ZLIB_BUILD_EXAMPLES OFF CACHE BOOL "Build zlib example programs" FORCE)
    FetchContent_MakeAvailable(zlib)
    set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${zlib_SOURCE_DIR};${zlib_BINARY_DIR}")
    add_library(ZLIB::ZLIB ALIAS zlibstatic)
    set(ZLIB_FOUND TRUE)
    set(ZLIB_LIBRARY zlibstatic) # (the find module's own names too: FreeType's build reads them)
    set(ZLIB_LIBRARIES zlibstatic)
    set(ZLIB_INCLUDE_DIR "${zlib_SOURCE_DIR};${zlib_BINARY_DIR}")
    set(ZLIB_INCLUDE_DIRS "${zlib_SOURCE_DIR};${zlib_BINARY_DIR}")
    set(ZLIB_VERSION_STRING "1.3.1 (built here)")
endif()
if(NOT ZLIB_FOUND)
    message(FATAL_ERROR "The Vita save importer requires zlib. Install zlib or enable CR_FETCH_DEPS.")
endif()
if(ZLIB_FOUND)
    target_link_libraries(cr3core PUBLIC ZLIB::ZLIB)
    target_compile_definitions(cr3core PUBLIC CR3_HAVE_ZLIB=1) # (public: the viewer's setup unpacks an .ipa)
    message(STATUS "APK: zlib ${ZLIB_VERSION_STRING}")
else()
    message(STATUS "APK: zlib not found - an unpacked APK only")
endif()
# The game's text: FreeType (the system's) draws the release's fonts (gfx/text.cpp); without it,
# no text
find_package(Freetype QUIET)
if(NOT FREETYPE_FOUND AND CR_FETCH_DEPS)
    include(FetchContent)
    set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
    set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE)
    set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE)
    set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE)
    set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE)
    set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE)
    FetchContent_Declare(freetype GIT_REPOSITORY https://github.com/freetype/freetype.git GIT_TAG VER-2-13-3 GIT_SHALLOW TRUE ${CR_FETCH_EXCLUDE})
    set(SKIP_INSTALL_ALL ON)
    set(cr_zlib_found ${ZLIB_FOUND})
    set(ZLIB_FOUND FALSE) # (FreeType links zlib whenever it is found, FT_DISABLE_ZLIB or not)
    FetchContent_MakeAvailable(freetype)
    set(ZLIB_FOUND ${cr_zlib_found})
    add_library(Freetype::Freetype ALIAS freetype)
    set(FREETYPE_FOUND TRUE)
    set(FREETYPE_VERSION_STRING "2.13.3 (built here)")
endif()
if(FREETYPE_FOUND)
    target_link_libraries(cr3core PUBLIC Freetype::Freetype)
    target_compile_definitions(cr3core PRIVATE CR3_HAVE_FREETYPE=1)
    message(STATUS "Text: FreeType ${FREETYPE_VERSION_STRING}")
else()
    message(STATUS "Text: FreeType not found - no text")
endif()

add_executable(cr3tool tools/cr3tool.cpp)
target_link_libraries(cr3tool PRIVATE cr3core)

# The viewer: SDL3 (the system's when present, else built from source as CR1:U does) and OpenGL 3.3
find_package(SDL3 CONFIG QUIET)
if(NOT SDL3_FOUND)
    include(FetchContent)
    FetchContent_Declare(SDL3
        GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
        GIT_TAG release-3.2.0
        GIT_SHALLOW TRUE)
    set(SDL_SHARED OFF CACHE BOOL "" FORCE)
    set(SDL_STATIC ON CACHE BOOL "" FORCE)
    FetchContent_MakeAvailable(SDL3)
endif()
# Vulkan back end: the loader + headers and glslc (shaders are compiled to SPIR-V at build time
# and embedded). Without them the game builds with OpenGL only.
find_package(Vulkan QUIET)
if(NOT Vulkan_FOUND AND MINGW AND CMAKE_CROSSCOMPILING)
    include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/mingw-vulkan.cmake) # (llvm-mingw: no Vulkan SDK)
endif()
find_program(GLSLC glslc HINTS ${Vulkan_GLSLC_EXECUTABLE})
set(CR_VULKAN OFF)
if(Vulkan_FOUND AND GLSLC AND NOT ANDROID) # (Android: GLES 3)
    set(CR_VULKAN ON)
    set(SHADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/gfx/shaders)
    set(SHADER_OUT ${CMAKE_CURRENT_BINARY_DIR}/shaders)
    file(MAKE_DIRECTORY ${SHADER_OUT})
    set(SHADER_INCS)
    foreach(s quad.vert quad.frag mesh.vert mesh.frag filter.vert filter.frag)
        add_custom_command(OUTPUT ${SHADER_OUT}/${s}.inc
            COMMAND ${GLSLC} -O -mfmt=c -I ${SHADER_DIR} ${SHADER_DIR}/${s} -o ${SHADER_OUT}/${s}.inc
            DEPENDS ${SHADER_DIR}/${s} ${SHADER_DIR}/common.glsl ${SHADER_DIR}/shading.glsl ${SHADER_DIR}/shade_flags.h
            COMMENT "glslc ${s}")
        list(APPEND SHADER_INCS ${SHADER_OUT}/${s}.inc)
    endforeach()
    # Scene variants (temporal upscaling): + motion vectors and a reactive mask
    foreach(s quad.vert quad.frag mesh.vert mesh.frag)
        add_custom_command(OUTPUT ${SHADER_OUT}/scene_${s}.inc
            COMMAND ${GLSLC} -O -mfmt=c -DSCENE=1 -I ${SHADER_DIR} ${SHADER_DIR}/${s} -o ${SHADER_OUT}/scene_${s}.inc
            DEPENDS ${SHADER_DIR}/${s} ${SHADER_DIR}/common.glsl ${SHADER_DIR}/shading.glsl ${SHADER_DIR}/shade_flags.h
            COMMENT "glslc ${s} (scene)")
        list(APPEND SHADER_INCS ${SHADER_OUT}/scene_${s}.inc)
    endforeach()
    # AMD FidelityFX FSR 3.1.6 upscaler passes (third_party/fidelityfx, MIT), as AMD's Vulkan
    # back end builds them: 32-bit maths, Lanczos approximation, render-size motion vectors
    set(FFX_DIR ${THIRD_PARTY_DIR}/fidelityfx)
    file(GLOB FFX_HEADERS ${FFX_DIR}/gpu/*.h ${FFX_DIR}/gpu/*/*.h)
    set(FFX_DEFS -DFFX_GPU=1 -DFFX_GLSL=1 -DFFX_HALF=0
        -DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_SAMPLERS_USE_DATA_HALF=0
        -DFFX_FSR3UPSCALER_OPTION_ACCUMULATE_SAMPLERS_USE_DATA_HALF=0
        -DFFX_FSR3UPSCALER_OPTION_REPROJECT_SAMPLERS_USE_DATA_HALF=0
        -DFFX_FSR3UPSCALER_OPTION_POSTPROCESSLOCKSTATUS_SAMPLERS_USE_DATA_HALF=0
        -DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE=2
        -DFFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE=1
        -DFFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT=0
        -DFFX_FSR3UPSCALER_OPTION_LOW_RESOLUTION_MOTION_VECTORS=1
        -DFFX_FSR3UPSCALER_OPTION_JITTERED_MOTION_VECTORS=0
        -DFFX_FSR3UPSCALER_OPTION_INVERTED_DEPTH=0)
    foreach(p prepare_inputs luma_pyramid shading_change_pyramid shading_change prepare_reactivity
              luma_instability accumulate accumulate_sharpen rcas)
        set(src ${p})
        set(sharpen 0)
        if(p STREQUAL "accumulate_sharpen")
            set(src accumulate)
            set(sharpen 1)
        endif()
        add_custom_command(OUTPUT ${SHADER_OUT}/fsr3_${p}.inc
            COMMAND ${GLSLC} -fshader-stage=comp --target-env=vulkan1.2 -O -mfmt=c ${FFX_DEFS}
                    -DFFX_FSR3UPSCALER_OPTION_APPLY_SHARPENING=${sharpen}
                    -I ${FFX_DIR}/gpu -I ${FFX_DIR}/gpu/fsr3upscaler
                    ${FFX_DIR}/passes/ffx_fsr3upscaler_${src}_pass.glsl -o ${SHADER_OUT}/fsr3_${p}.inc
            DEPENDS ${FFX_DIR}/passes/ffx_fsr3upscaler_${src}_pass.glsl ${FFX_HEADERS}
            COMMENT "glslc FSR 3.1 ${p}")
        list(APPEND SHADER_INCS ${SHADER_OUT}/fsr3_${p}.inc)
    endforeach()
    add_custom_target(cr_shaders DEPENDS ${SHADER_INCS})
    include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/dlss.cmake) # (NVIDIA DLSS: CR_DLSS, cr_link_dlss)
    if(NOT MSVC)
        set_source_files_properties(src/gfx/vk_device.cpp src/gfx/vk_fsr3.cpp src/gfx/vk_dlss.cpp src/gfx/vk_fsr4.cpp
                                    PROPERTIES COMPILE_OPTIONS -Wno-missing-field-initializers)
    endif()
    message(STATUS "Renderer: Vulkan ${Vulkan_VERSION} (the default) and OpenGL")
else()
    message(STATUS "Renderer: OpenGL only (Vulkan loader or glslc not found)")
endif()

# The shaders' shared GLSL as C++ strings, for the GL device (Vulkan's are compiled by glslc)
set(GLSL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/gfx/shaders)
set(GLSL_OUT ${CMAKE_CURRENT_BINARY_DIR}/glsl)
file(MAKE_DIRECTORY ${GLSL_OUT})
set(GLSL_INCS)
foreach(f shade_flags.h shading.glsl)
    add_custom_command(OUTPUT ${GLSL_OUT}/${f}.inc
        COMMAND ${CMAKE_COMMAND} -DIN=${GLSL_DIR}/${f} -DOUT=${GLSL_OUT}/${f}.inc -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed.cmake
        DEPENDS ${GLSL_DIR}/${f} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed.cmake
        COMMENT "embed ${f}")
    list(APPEND GLSL_INCS ${GLSL_OUT}/${f}.inc)
endforeach()
add_custom_target(cr_glsl DEPENDS ${GLSL_INCS})

set(VIEWER_SOURCES
    src/viewer/main.cpp
    src/viewer/viewer_gfx.cpp
    src/viewer/viewer_field.cpp
    src/viewer/viewer_battle.cpp
    src/viewer/viewer_event.cpp
    src/viewer/viewer_spaceport.cpp
    src/viewer/viewer_camp.cpp
    src/viewer/audio.cpp
    src/viewer/setup.cpp
    src/gfx/gl.cpp
    src/gfx/gl_device.cpp
    src/gfx/vk_device.cpp)
if(CR_VULKAN)
    list(APPEND VIEWER_SOURCES src/gfx/vk_fsr3.cpp src/gfx/vk_dlss.cpp src/gfx/vk_fsr4.cpp)
    if(APPLE) # (MetalFX's upscaler: Objective-C++)
        list(APPEND VIEWER_SOURCES src/gfx/vk_metalfx.mm)
        set_source_files_properties(src/gfx/vk_metalfx.mm PROPERTIES COMPILE_OPTIONS "-fobjc-arc")
        include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/apple-vulkan.cmake)
    endif()
endif()
if(ANDROID)
    # SDL's Android Java glue loads libmain.so (android/: the Gradle project)
    add_library(cr3view SHARED ${VIEWER_SOURCES})
    set_target_properties(cr3view PROPERTIES OUTPUT_NAME main)
    target_link_libraries(cr3view PRIVATE log android)
else()
    add_executable(cr3view WIN32 MACOSX_BUNDLE ${VIEWER_SOURCES})
endif()
# The GL device on OpenGL ES 3.0: Android's; elsewhere (Mesa's GLES) a test of that path
option(CR3_GLES "OpenGL ES 3.0 for the GL device" ${ANDROID})
if(CR3_GLES)
    find_library(CR3_GLES_LIBRARY NAMES GLESv3 GLESv2 REQUIRED)
    target_link_libraries(cr3view PRIVATE ${CR3_GLES_LIBRARY})
    target_compile_definitions(cr3view PRIVATE CR_GLES=1)
    message(STATUS "GL device: OpenGL ES 3.0 (${CR3_GLES_LIBRARY})")
endif()
target_link_libraries(cr3view PRIVATE cr3core SDL3::SDL3)
# The setup (viewer/setup.cpp): a Vita NoNpDrm dump's files decrypted (third_party/psvpfsparser -
# Vita3K's, GPL-2.0) - the first start's, a phone's too, and one copied into vita/ on any start -,
# and a dump's archive - .rar, .7z, .zip, .vpk - unpacked (libarchive: the system's, else built here
# - CR_FETCH_DEPS; without it, a folder only)
if(NOT IOS)
    file(GLOB CR_PSVPFS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/third_party/psvpfsparser/*.cpp)
    add_library(psvpfs STATIC ${CR_PSVPFS_SOURCES})
    target_include_directories(psvpfs SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/psvpfsparser)
    target_compile_features(psvpfs PUBLIC cxx_std_17)
    if(NOT MSVC)
        target_compile_options(psvpfs PRIVATE -w)
    endif()
    target_link_libraries(cr3view PRIVATE psvpfs)
    target_compile_definitions(cr3view PRIVATE CR_HAVE_PSVPFS=1)
endif()
if(NOT IOS) # (a phone's setup too: a dump or the fan translation picked as its archive - Android's libarchive without LZMA: no .7z)
    find_package(LibArchive QUIET)
    if(NOT LibArchive_FOUND AND CR_FETCH_DEPS)
        include(FetchContent)
        set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
        foreach(off ENABLE_OPENSSL ENABLE_MBEDTLS ENABLE_NETTLE ENABLE_LIBB2 ENABLE_LZ4 ENABLE_LZO ENABLE_ZSTD ENABLE_BZip2 ENABLE_LIBXML2
                    ENABLE_EXPAT ENABLE_PCREPOSIX ENABLE_PCRE2POSIX ENABLE_LIBGCC ENABLE_CNG ENABLE_TAR ENABLE_CPIO ENABLE_CAT ENABLE_UNZIP
                    ENABLE_XATTR ENABLE_ACL ENABLE_ICONV ENABLE_TEST ENABLE_INSTALL ENABLE_WERROR)
            set(${off} OFF CACHE BOOL "" FORCE)
        endforeach()
        set(POSIX_REGEX_LIB NONE CACHE STRING "" FORCE) # (no regular expressions: only its readers are used)
        if(MSVC) # (its checks miss the C runtime's wide-string functions under MSVC, then it defines its own: C2169)
            foreach(have HAVE_WCSCPY HAVE_WCSLEN HAVE_WMEMCMP HAVE_WMEMCPY HAVE_WMEMMOVE)
                set(${have} 1 CACHE INTERNAL "")
            endforeach()
        endif()
        # (7z's LZMA - the CR3 fan translation comes as one -: the system's liblzma, else xz's built here)
        find_package(LibLZMA QUIET)
        if(NOT LIBLZMA_FOUND)
            foreach(off XZ_TOOL_XZ XZ_TOOL_XZDEC XZ_TOOL_LZMADEC XZ_TOOL_LZMAINFO XZ_TOOL_SCRIPTS XZ_TOOL_SYMLINKS XZ_TOOL_SYMLINKS_LZMA XZ_NLS XZ_DOXYGEN BUILD_TESTING)
                set(${off} OFF CACHE BOOL "" FORCE)
            endforeach()
            FetchContent_Declare(xz GIT_REPOSITORY https://github.com/tukaani-project/xz.git GIT_TAG v5.8.1 GIT_SHALLOW TRUE ${CR_FETCH_EXCLUDE})
            FetchContent_MakeAvailable(xz)
            # (libarchive's find: this liblzma by its target's name, its checks taken as passed - it isn't built yet)
            set(LIBLZMA_INCLUDE_DIR ${xz_SOURCE_DIR}/src/liblzma/api CACHE PATH "" FORCE)
            set(LIBLZMA_LIBRARY liblzma CACHE STRING "" FORCE)
            set(LIBLZMA_LIBRARY_RELEASE liblzma CACHE STRING "" FORCE) # (newer CMake's find takes the library from this)
            foreach(have LIBLZMA_HAS_AUTO_DECODER LIBLZMA_HAS_EASY_ENCODER LIBLZMA_HAS_LZMA_PRESET)
                set(${have} TRUE CACHE INTERNAL "")
            endforeach()
        endif()
        FetchContent_Declare(libarchive GIT_REPOSITORY https://github.com/libarchive/libarchive.git GIT_TAG v3.7.7 GIT_SHALLOW TRUE ${CR_FETCH_EXCLUDE})
        FetchContent_MakeAvailable(libarchive)
        target_include_directories(archive_static INTERFACE ${libarchive_SOURCE_DIR}/libarchive)
        target_compile_definitions(archive_static INTERFACE LIBARCHIVE_STATIC)
        add_library(LibArchive::LibArchive ALIAS archive_static)
        set(LibArchive_FOUND TRUE)
        set(LibArchive_VERSION "3.7.7 (built here)")
    endif()
    if(LibArchive_FOUND)
        target_link_libraries(cr3view PRIVATE LibArchive::LibArchive)
        target_compile_definitions(cr3view PRIVATE CR_HAVE_LIBARCHIVE=1)
        message(STATUS "Setup: Vita dumps decrypted; archives through libarchive ${LibArchive_VERSION}")
    else()
        message(STATUS "Setup: Vita dumps decrypted; no libarchive - a dump's archive unpacked first")
    endif()
endif()
set_target_properties(cr3view PROPERTIES
    MACOSX_BUNDLE_BUNDLE_NAME "Chaos Rings III"
    MACOSX_BUNDLE_GUI_IDENTIFIER "local.preservation.chaosrings3")
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/app-icon.cmake)
cr_app_icon(cr3view chaosrings3)
add_dependencies(cr3view cr_glsl)
target_include_directories(cr3view PRIVATE ${GLSL_OUT})
if(CR_VULKAN)
    add_dependencies(cr3view cr_shaders)
    target_include_directories(cr3view PRIVATE ${SHADER_OUT})
    target_link_libraries(cr3view PRIVATE Vulkan::Vulkan)
    if(MSVC) # (no Vulkan on the PC: the game still starts, SDL's window refused and OpenGL tried)
        target_link_options(cr3view PRIVATE /DELAYLOAD:vulkan-1.dll)
        target_link_libraries(cr3view PRIVATE delayimp)
    endif()
    if(APPLE)
        cr_apple_vulkan(cr3view)
    endif()
    target_compile_definitions(cr3view PRIVATE CR_HAVE_VULKAN=1)
    # AMD FSR 4: the FSR API's headers (third_party/ffx, MIT); AMD's DLLs are loaded at run time from fsr4/
    target_include_directories(cr3view SYSTEM PRIVATE ${THIRD_PARTY_DIR}/ffx/api/include ${THIRD_PARTY_DIR}/ffx/upscalers/include)
    if(CR_DLSS)
        cr_link_dlss(cr3view)
    endif()
endif()

# In-process save importer tests, with synthetic exports and no game assets.
if(NOT ANDROID AND NOT IOS)
    enable_testing()
    add_executable(vita-import-tests tools/vita_import_test.cpp)
    add_executable(battle-target-tests tools/battle_target_test.cpp)
    target_link_libraries(battle-target-tests PRIVATE cr3core SDL3::SDL3)
    add_test(NAME battle-target-side COMMAND battle-target-tests)
    target_link_libraries(vita-import-tests PRIVATE cr3core SDL3::SDL3)
    add_test(NAME vita-import COMMAND vita-import-tests)
    find_package(Threads REQUIRED)
    add_executable(input-cancel-tests tools/input_cancel_test.cpp)
    target_include_directories(input-cancel-tests PRIVATE src)
    target_link_libraries(input-cancel-tests PRIVATE SDL3::SDL3 Threads::Threads)
    add_test(NAME input-cancel COMMAND input-cancel-tests)
    add_executable(lifecycle-clock-tests tools/lifecycle_clock_test.cpp)
    target_include_directories(lifecycle-clock-tests PRIVATE src)
    target_link_libraries(lifecycle-clock-tests PRIVATE SDL3::SDL3 Threads::Threads)
    add_test(NAME lifecycle-clock COMMAND lifecycle-clock-tests)
    add_executable(gl-context-tests tools/gl_context_test.cpp src/gfx/gl.cpp src/gfx/gl_device.cpp)
    add_dependencies(gl-context-tests cr_glsl)
    target_include_directories(gl-context-tests PRIVATE ${GLSL_OUT})
    target_link_libraries(gl-context-tests PRIVATE cr3core SDL3::SDL3)
    if(CR3_GLES)
        target_compile_definitions(gl-context-tests PRIVATE CR_GLES=1)
        target_link_libraries(gl-context-tests PRIVATE ${CR3_GLES_LIBRARY})
    endif()
    add_test(NAME gl-context-recovery COMMAND gl-context-tests)
    set_tests_properties(gl-context-recovery PROPERTIES SKIP_RETURN_CODE 77)

    add_test(NAME viewer-input-cancel COMMAND cr3view --selftest-input-cancel)
    add_test(NAME viewer-lifecycle-clock COMMAND cr3view --selftest-lifecycle-clock)
    add_executable(audio-player-tests tools/audio_player_test.cpp src/viewer/audio.cpp)
    target_link_libraries(audio-player-tests PRIVATE cr3core SDL3::SDL3)
    add_test(NAME audio-player COMMAND audio-player-tests)
    add_test(NAME viewer-scene-audio COMMAND cr3view --selftest-scene-audio)
    add_test(NAME viewer-session-ui COMMAND cr3view --selftest-session-ui)
    add_test(NAME viewer-field-restore COMMAND cr3view --selftest-field-restore)
    add_test(NAME viewer-new-session COMMAND cr3view --selftest-new-session)
    add_test(NAME viewer-save-slots COMMAND cr3view --selftest-save-slots)
    add_test(NAME viewer-vista-pause COMMAND cr3view --selftest-vista-pause)
    add_test(NAME viewer-quake COMMAND cr3view --selftest-quake)
    set_tests_properties(viewer-vista-pause PROPERTIES SKIP_RETURN_CODE 77)
    add_executable(gom-camera-tests tools/gom_camera_test.cpp)
    target_link_libraries(gom-camera-tests PRIVATE cr3core)
    add_test(NAME gom-camera COMMAND gom-camera-tests)
    if(UNIX)
        # (the Vita's buttons over its own copy: CR3_VITA_DATA the Vita copy's folder, else skipped)
        add_test(NAME viewer-pad-vita COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/tools/pad_vita_test.sh $<TARGET_FILE:cr3view>)
        set_tests_properties(viewer-pad-vita PROPERTIES SKIP_RETURN_CODE 77 RUN_SERIAL TRUE TIMEOUT 1800)
    endif()
    add_executable(atomic-file-tests tools/atomic_file_test.cpp)
    target_include_directories(atomic-file-tests PRIVATE src)
    target_link_libraries(atomic-file-tests PRIVATE SDL3::SDL3)
    add_test(NAME atomic-file COMMAND atomic-file-tests)
    add_executable(hd-texture-tests tools/hd_texture_test.cpp)
    target_include_directories(hd-texture-tests PRIVATE src third_party)
    target_link_libraries(hd-texture-tests PRIVATE cr3core)
    add_test(NAME hd-texture COMMAND hd-texture-tests)
    add_executable(card-art-tests tools/card_art_test.cpp)
    target_link_libraries(card-art-tests PRIVATE cr3core)
    add_test(NAME card-art COMMAND card-art-tests)
    add_executable(mobile-input-tests tools/mobile_input_test.cpp)
    target_link_libraries(mobile-input-tests PRIVATE cr3core SDL3::SDL3)
    target_compile_definitions(mobile-input-tests PRIVATE TEST_NAMESPACE=cr3 TEST_CR3=1)
    add_test(NAME mobile-input COMMAND mobile-input-tests)
    add_executable(save-integrity-tests tools/save_integrity_test.cpp)
    target_link_libraries(save-integrity-tests PRIVATE cr3core SDL3::SDL3)
    target_compile_definitions(save-integrity-tests PRIVATE TEST_NAMESPACE=cr3 SAVE_LAST_REQUIRED="area")
    add_test(NAME save-integrity COMMAND save-integrity-tests)
    add_executable(event-reset-tests tools/event_reset_test.cpp)
    target_link_libraries(event-reset-tests PRIVATE cr3core SDL3::SDL3)
    target_compile_definitions(event-reset-tests PRIVATE TEST_NAMESPACE=cr3 TEST_CR3=1)
    add_test(NAME event-reset COMMAND event-reset-tests)
    if(TARGET cr3_atrac9)
        add_executable(atrac9-bits-tests tools/atrac9_bits_test.cpp)
        target_link_libraries(atrac9-bits-tests PRIVATE cr3_atrac9)
        add_test(NAME atrac9-bits COMMAND atrac9-bits-tests)
    endif()
    add_executable(new-game-plus-tests tools/new_game_plus_test.cpp)
    target_link_libraries(new-game-plus-tests PRIVATE cr3core)
    add_test(NAME new-game-plus COMMAND new-game-plus-tests)
    add_executable(tab-buttons-tests tools/tab_buttons_test.cpp)
    target_link_libraries(tab-buttons-tests PRIVATE cr3core)
    add_test(NAME tab-buttons COMMAND tab-buttons-tests)
endif()
