STATIC_LINKING := 0
AR             := ar
CORE           := -DCORE=ON
DEBUG          := 0
RELWITHDEBINFO := 0
FORCE_GLES     := 0
GLES_VER       ?= 2
HAVE_VR        ?= 0
# the GLSL renderers (ES 2, ES 3, ES 3.1) in desktop cores, beside ARB2
HAVE_GLSL_RENDERERS ?= 1
# the Vulkan renderer, on desktop builds; the frontend serves it a Vulkan context
HAVE_VULKAN_RENDERER ?= 1

ifneq ($(SANITIZER),)
   CFLAGS   := -fsanitize=$(SANITIZER) $(CFLAGS)
   CXXFLAGS := -fsanitize=$(SANITIZER) $(CXXFLAGS)
   LDFLAGS  := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif

ifeq ($(platform),)
platform = unix
ifeq ($(shell uname -a),)
   platform = win
else ifneq ($(findstring MINGW,$(shell uname -a)),)
   platform = win
else ifneq (,$(findstring mingw,$(CXX)))
   platform = win
else ifneq ($(findstring Darwin,$(shell uname -a)),)
   platform = osx
else ifneq ($(findstring win,$(shell uname -a)),)
   platform = win
endif
endif

CORE_DIR    += .
# One unified core: the game module is the d3xp (RoE) code, a superset of
# the base game; title selection happens at runtime via fs_game.
TARGET_NAME := boom3
LIBM		    = -lm

ifeq ($(ARCHFLAGS),)
ifeq ($(archs),ppc)
   ARCHFLAGS = -arch ppc -arch ppc64
else ifeq ($(shell uname -m),arm64)
   ARCHFLAGS = -arch arm64
else
   ARCHFLAGS = -arch x86_64
endif
endif

ifeq ($(platform), osx)
ifndef ($(NOUNIVERSAL))
   CXXFLAGS += $(ARCHFLAGS)
   LDFLAGS += $(ARCHFLAGS)
endif
endif

ifeq ($(STATIC_LINKING), 1)
EXT := a
endif

CFLAGS += -DLIBRETRO
CXXFLAGS += -DLIBRETRO

ifneq (,$(findstring unix,$(platform)))
	EXT ?= so
   TARGET := $(TARGET_NAME)_libretro.$(EXT)
   fpic := -fPIC
   SHARED := -shared -Wl,--version-script=$(CORE_DIR)/link.T -Wl,--no-undefined
   LIBS := -ldl
   ifneq ($(HAVE_THREADS), 0)
      LIBS += -lpthread
   endif
   # libretro-common's memmap.c calls shm_open, which glibc before 2.34
   # keeps in librt
   ifneq (,$(findstring Linux,$(shell uname -s)))
      LIBS += -lrt
   endif
   ifeq ($(FORCE_GLES),1)
      GL_LIB := -lGLESv2
      CFLAGS += -DHAVE_OPENGLES
      CXXFLAGS += -DHAVE_OPENGLES
      GLES = 1
      ifeq ($(GLES_VER),3)
         CFLAGS += -DHAVE_OPENGLES3
         CXXFLAGS += -DHAVE_OPENGLES3
      else
         CFLAGS += -DHAVE_OPENGLES2
         CXXFLAGS += -DHAVE_OPENGLES2
      endif
   else
	GL_LIB := -lGL
   endif
else ifeq ($(platform), linux-portable)
   TARGET := $(TARGET_NAME)_libretro.$(EXT)
   fpic := -fPIC -nostdlib
   SHARED := -shared -Wl,--version-script=$(CORE_DIR)/link.T
	LIBM :=
   ifeq ($(FORCE_GLES),1)
      GL_LIB := -lGLESv2
      CFLAGS += -DHAVE_OPENGLES
      CXXFLAGS += -DHAVE_OPENGLES
      GLES = 1
      ifeq ($(GLES_VER),3)
         CFLAGS += -DHAVE_OPENGLES3
         CXXFLAGS += -DHAVE_OPENGLES3
      else
         CFLAGS += -DHAVE_OPENGLES2
         CXXFLAGS += -DHAVE_OPENGLES2
      endif
   else
	GL_LIB := -lGL
   endif
else ifeq ($(platform), osx-arm64)
   TARGET := $(TARGET_NAME)_libretro.dylib
   fpic := -fPIC
   SHARED := -dynamiclib
   GL_LIB := -framework OpenGL
   CC = cc -arch arm64
   CXX = c++ -arch arm64
   CFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined
   CXXFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined
else ifeq ($(platform), osx-x64)
   TARGET := $(TARGET_NAME)_libretro.dylib
   fpic := -fPIC
   SHARED := -dynamiclib
   GL_LIB := -framework OpenGL
   CC = cc -arch x86_64
   CXX = c++ -arch x86_64
   CFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined
   CXXFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined
else ifneq (,$(findstring osx,$(platform)))
   TARGET := $(TARGET_NAME)_libretro.dylib
   fpic := -fPIC
   SHARED := -dynamiclib
   GL_LIB := -framework OpenGL
   CFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined
   CXXFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined
else ifeq ($(platform), ios-arm64)
   TARGET := $(TARGET_NAME)_libretro_ios.dylib
   fpic := -fPIC
   SHARED := -dynamiclib
   GL_LIB := -framework OpenGLES
   GLES = 1
   CORE =
   ifeq ($(IOSSDK),)
      IOSSDK := $(shell xcodebuild -version -sdk iphoneos Path)
   endif
   CC = cc -arch arm64 -isysroot $(IOSSDK) -miphoneos-version-min=11.0
   CXX = c++ -arch arm64 -isysroot $(IOSSDK) -miphoneos-version-min=11.0
   CFLAGS += -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DIOS -DGLES_SILENCE_DEPRECATION
   CXXFLAGS += -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DIOS -DGLES_SILENCE_DEPRECATION
else ifeq ($(platform), tvos-arm64)
   TARGET := $(TARGET_NAME)_libretro_tvos.dylib
   fpic := -fPIC
   SHARED := -dynamiclib
   GL_LIB := -framework OpenGLES
   GLES = 1
   CORE =
   ifeq ($(TVOSSDK),)
      TVOSSDK := $(shell xcodebuild -version -sdk appletvos Path)
   endif
   CC = cc -arch arm64 -isysroot $(TVOSSDK) -mtvos-version-min=11.0
   CXX = c++ -arch arm64 -isysroot $(TVOSSDK) -mtvos-version-min=11.0
   CFLAGS += -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DIOS -DGLES_SILENCE_DEPRECATION
   CXXFLAGS += -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DIOS -DGLES_SILENCE_DEPRECATION
else ifneq (,$(findstring qnx,$(platform)))
	TARGET := $(TARGET_NAME)_libretro_qnx.so
   fpic := -fPIC
   SHARED := -shared -Wl,--version-script=$(CORE_DIR)/link.T -Wl,--no-undefined
else ifeq ($(platform), emscripten)
   TARGET := $(TARGET_NAME)_libretro_emscripten.bc
   fpic := -fPIC
   SHARED := -shared -Wl,--version-script=$(CORE_DIR)/link.T -Wl,--no-undefined
else ifeq ($(platform), vita)
   TARGET := $(TARGET_NAME)_vita.a
   CC = arm-vita-eabi-gcc
   AR = arm-vita-eabi-ar
   CXXFLAGS += -Wl,-q -Wall -O3
   STATIC_LINKING = 1
# Nintendo Switch (libnx)
else ifeq ($(platform), libnx)
    include $(DEVKITPRO)/libnx/switch_rules
    EXT=a
    TARGET := $(TARGET_NAME)_libretro_$(platform).$(EXT)
    DEFINES := -DSWITCH=1 -D__SWITCH__=1 -DHAVE_LIBNX=1
    CFLAGS	:=	 $(DEFINES) -g -fPIE -I$(LIBNX)/include/ -I$(PORTLIBS)/include/ -ffunction-sections -fdata-sections -ftls-model=local-exec
    CFLAGS += $(INCDIRS) \
		-fvisibility=hidden
    CFLAGS	+= -march=armv8-a -mtune=cortex-a57 -mtp=soft -mcpu=cortex-a57+crc+fp+simd
    CXXFLAGS := $(ASFLAGS) $(CFLAGS) -std=gnu++11
    STATIC_LINKING = 1
else ifneq (,$(findstring android,$(platform)))
   TARGET := $(TARGET_NAME)_libretro_android.so
   fpic := -fPIC
   SHARED := -shared -Wl,--version-script=$(CORE_DIR)/link.T -Wl,--no-undefined
   LIBS := -ldl
   GL_LIB := -lGLESv2
   CFLAGS += -DHAVE_OPENGLES
   CXXFLAGS += -DHAVE_OPENGLES
   GLES = 1
   ifeq ($(GLES_VER),3)
      CFLAGS += -DHAVE_OPENGLES3
      CXXFLAGS += -DHAVE_OPENGLES3
      GL_LIB := -lGLESv3
   else
      CFLAGS += -DHAVE_OPENGLES2
      CXXFLAGS += -DHAVE_OPENGLES2
   endif
   LDFLAGS += -static-libstdc++
   CORE =
   ifeq ($(origin CC), default)
      ifneq ($(ANDROID_NDK_LLVM),)
        NDK_BIN := $(ANDROID_NDK_LLVM)/bin
        API := 21
        ifeq ($(platform),android-arm64)
            TARGET_TRIPLE := aarch64-linux-android
         else ifeq ($(platform),android-arm)
            TARGET_TRIPLE := armv7a-linux-androideabi
         else ifeq ($(platform),android-x86)
            TARGET_TRIPLE := i686-linux-android
         else ifeq ($(platform),android-x86_64)
            TARGET_TRIPLE := x86_64-linux-android
        endif
        CC  := $(NDK_BIN)/$(TARGET_TRIPLE)$(API)-clang
        CXX := $(NDK_BIN)/$(TARGET_TRIPLE)$(API)-clang++
      endif
   endif
else
# Windows
   CC ?= gcc
   TARGET := $(TARGET_NAME)_libretro.dll
   SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=$(CORE_DIR)/link.T -Wl,--no-undefined
   GL_LIB := -lopengl32
   LDFLAGS += -lwsock32 -lws2_32
   LIBS :=
endif

# 1. If CROSS_COMPILE is a real prefix (contains a dash), derive COMPILE_ARCH from it
ifneq ($(findstring -,$(CROSS_COMPILE)),)
    COMPILE_ARCH := $(firstword $(subst -, ,$(CROSS_COMPILE)))
endif

# 2. If platform is android, derive from CC instead
ifeq ($(strip $(COMPILE_ARCH)),)
ifneq (,$(findstring android,$(platform)))
    COMPILE_ARCH := $(firstword $(subst -, ,$(notdir $(CC))))
endif
endif

# 3. If still empty, try CC -dumpmachine (catches i386 docker on x86_64 host)
ifeq ($(strip $(COMPILE_ARCH)),)
    COMPILE_ARCH := $(shell $(CC) -dumpmachine 2>/dev/null | cut -d- -f1)
endif

# 4. If still empty, fall back to uname -m
ifeq ($(strip $(COMPILE_ARCH)),)
    COMPILE_ARCH := $(shell uname -m | sed -e 's/amd64/x86_64/' -e 's/x64/x86_64/' -e 's/powerpc$$/ppc/' -e 's/powerpc64/ppc64/' -e 's/aarch64/arm64/')
endif

ifeq ($(platform),libnx)
    COMPILE_ARCH := aarch64
endif

# Normalize i?86 to x86
COMPILE_ARCH := $(shell echo $(COMPILE_ARCH) | sed 's/i[3456]86/x86/')

# Default to 64-bit pointer size
D3_SIZEOFPTR := 8

ifeq ($(COMPILE_ARCH),powerpc)
    D3_ARCH := ppc
    MSB_FIRST := 1
    D3_SIZEOFPTR := 4
else ifeq ($(COMPILE_ARCH),powerpc64)
    D3_ARCH := ppc64
    MSB_FIRST := 1
else ifeq ($(COMPILE_ARCH),aarch64)
    D3_ARCH := aarch64
    MSB_FIRST := 0
else ifeq ($(COMPILE_ARCH),arm64)
    D3_ARCH := aarch64
    MSB_FIRST := 0
else ifeq ($(COMPILE_ARCH),x86_64)
    D3_ARCH := x86_64
    MSB_FIRST := 0
else ifeq ($(COMPILE_ARCH),x86)
    D3_ARCH := x86
    MSB_FIRST := 0
    D3_SIZEOFPTR := 4
    # Evaluate float expressions in SSE registers rather than the x87 stack.
    # x87 computes at 80-bit internally and rounds to 32-bit at points the
    # compiler chooses, so the same source produces different results here
    # than on x86_64/ARM. -ffp-contract=off does not help: the excess
    # precision is in the register file, not in fused operations. SSE2 is
    # baseline on every x86 CPU since 2003.
    ARCH_FP_FLAGS := -msse -msse2 -mfpmath=sse
else ifneq (,$(findstring arm,$(COMPILE_ARCH)))
    D3_ARCH := arm
    MSB_FIRST := 0
    D3_SIZEOFPTR := 4
else ifneq (,$(findstring mips,$(COMPILE_ARCH)))
    D3_ARCH := mips
    MSB_FIRST := 0
else ifneq (,$(findstring sparc,$(COMPILE_ARCH)))
    D3_ARCH := sparc
    MSB_FIRST := 1
else
    D3_ARCH := $(COMPILE_ARCH)
    MSB_FIRST := 0
endif

# Equivalent of CMake's D3_OSTYPE and D3_SIZEOFPTR
ifneq (,$(findstring osx,$(platform)))
    D3_OSTYPE := macosx
else ifeq ($(platform), win)
    D3_OSTYPE := windows
else ifneq (,$(findstring mingw,$(CXX)))
    D3_OSTYPE := windows
else ifneq (,$(findstring tvos,$(platform)))
    D3_OSTYPE := tvos
else ifneq (,$(findstring ios,$(platform)))
    D3_OSTYPE := ios
else
    D3_OSTYPE := linux
endif

ifeq ($(HAVE_VR),1)
   CFLAGS += -DBOOM3_VR
   CXXFLAGS += -DBOOM3_VR
endif

ifeq ($(platform), libnx)
   HAVE_GLSL_RENDERERS := 0
endif
# the Vulkan renderer shares the GLSL renderers' engine side (renderer
# option, GL no-op entry points): it goes where they go
ifneq ($(HAVE_GLSL_RENDERERS),1)
   HAVE_VULKAN_RENDERER := 0
endif
ifneq ($(GLES),1)
ifeq ($(HAVE_GLSL_RENDERERS),1)
   CFLAGS += -DHAVE_GLSL_RENDERERS
   CXXFLAGS += -DHAVE_GLSL_RENDERERS
endif
endif

CFLAGS += -DD3_OSTYPE=\"$(D3_OSTYPE)\" -DD3_SIZEOFPTR=$(D3_SIZEOFPTR) -DD3_ARCH=\"$(D3_ARCH)\"
# per-architecture floating point evaluation flags (see the arch block above)
CFLAGS   += $(ARCH_FP_FLAGS)
CXXFLAGS += $(ARCH_FP_FLAGS)
# libretro convention: MSB_FIRST is defined (with no value) only on
# big-endian targets
ifeq ($(MSB_FIRST), 1)
CFLAGS   += -DMSB_FIRST
CXXFLAGS += -DMSB_FIRST
endif

# webOS
ifneq (,$(or $(findstring webos,$(CROSS_COMPILE)),$(findstring starfish,$(CROSS_COMPILE))))
   GLES = 1
   HAVE_VULKAN_RENDERER = 0
   # CORE is for OpenGL or GLES 3.x
   CORE =
   GL_LIB := -lGLESv2
   CFLAGS += -DHAVE_OPENGLES -DHAVE_OPENGLES2
   CXXFLAGS += -DHAVE_OPENGLES -DHAVE_OPENGLES2
endif

ifeq ($(HAVE_VULKAN_RENDERER),1)
   # the vendored Vulkan headers: no SDK on the build machine
   CFLAGS += -DHAVE_VULKAN_RENDERER -I$(CORE_DIR)/renderer/vk/include
   CXXFLAGS += -DHAVE_VULKAN_RENDERER -I$(CORE_DIR)/renderer/vk/include
endif

LDFLAGS += $(LIBM)

ifeq ($(DEBUG),1)
  CFLAGS += -O0 -g -DDEBUG
  CXXFLAGS += -O0 -g -DDEBUG
else ifeq ($(RELWITHDEBINFO),1)
  CFLAGS  += -O2 -g -fno-omit-frame-pointer
  CXXFLAGS += -O2 -g -fno-omit-frame-pointer
else
  CFLAGS += -O3
  CXXFLAGS += -O3
endif

include Makefile.common

OBJECTS := $(SOURCES_C:.c=.o) $(SOURCES_CXX:.cpp=.o)
OBJECTS := $(filter-out %.c %.cpp,$(OBJECTS))

TOPDIR ?= $(CURDIR)



CFLAGS   += -Wall $(fpic) $(CORE) -DBASE=ON -ffast-math \
			-fno-unsafe-math-optimizations -ffp-contract=off \
			-fno-strict-aliasing -fomit-frame-pointer \
			-Wno-sign-compare -Wno-switch -Wno-format-security -I$(TOPDIR) -I$(TOPDIR)/sys/libretro/config
# libretro-common image_transfer: enable only the decoders Doom 3 uses
# (TGA source images, DDS precompressed, JPEG for mod content that ships .jpg
# instead of .tga - see LoadJPG()). png/bmp/webp stay off.
CFLAGS   += -DHAVE_RTGA -DHAVE_RDDS -DHAVE_RJPEG
CXXFLAGS += -DHAVE_RTGA -DHAVE_RDDS -DHAVE_RJPEG
# libretro-common audio_transfer: enable WAV and OGG Vorbis decoders
# (Doom 3 ships audio as WAV and OGG). FLAC/MP3 stay off.
CFLAGS   += -DHAVE_RVORBIS -DHAVE_RWAV
CXXFLAGS += -DHAVE_RVORBIS -DHAVE_RWAV
CXXFLAGS += -Wall $(fpic) -fpermissive -std=gnu++11
# Float determinism + aliasing, C++ side. The CFLAGS block above carries
# -ffast-math -fno-unsafe-math-optimizations -ffp-contract=off
# -fno-strict-aliasing, but nothing ever copied it into CXXFLAGS, so the
# entire engine - including every snd_*.cpp of the deterministic mixer -
# compiled with GCC's default -ffp-contract=fast (FMA contraction on
# aarch64 and any -mfma x86) and strict aliasing at -O3. The x86 arch
# comment above reasons about -ffp-contract=off as if it were in effect
# engine-wide; it was only in effect for C.
#
# Deliberately NOT copying -ffast-math to C++: it implies
# -ffinite-math-only, which licenses the compiler to delete the engine's
# idMath NaN checks. The vendored C code doesn't lean on NaN semantics;
# the engine does.
CXXFLAGS += -ffp-contract=off -fno-strict-aliasing

# Sanitizer sweep: make SANITIZE=address,undefined (or thread, or leak).
# Instruments the whole core; run the game under a frontend with
# ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 in the
# environment and LD_PRELOAD of the matching libasan if the frontend
# itself is uninstrumented. -O1 keeps stacks honest.
ifneq ($(SANITIZE),)
# vptr checking needs RTTI, and parts of this tree build -fno-rtti:
# the mismatch surfaces as undefined typeinfo references at link.
CFLAGS   += -fsanitize=$(SANITIZE) -fno-sanitize=vptr -fno-omit-frame-pointer -g -O1
CXXFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize=vptr -fno-omit-frame-pointer -g -O1
LDFLAGS  += -fsanitize=$(SANITIZE) -fno-sanitize=vptr
endif

# POSIX mmap for the VFS (pak mapping fast path, data_transfer windows).
# Only platforms whose libc has mmap: desktop unix and android. Windows'
# vendored VFS has no mapping arm and consoles lack mmap - they take the
# buffered-RFILE fallback in ioapi_mapped.cpp automatically.
ifneq (,$(filter unix linux%,$(platform)))
CFLAGS   += -DHAVE_MMAP
CXXFLAGS += -DHAVE_MMAP
endif
ifneq (,$(findstring android,$(platform)))
CFLAGS   += -DHAVE_MMAP
CXXFLAGS += -DHAVE_MMAP
endif
# idlib deliberately memset()s/memcpy()s POD-ish structs and classes that the
# compiler considers non-trivial only because they have constructors; rewriting
# ~60 of those to value-initialisation would be churn with no runtime benefit.
# The binding tables in sys/libretro/libretro.cpp likewise store string literals
# in char* fields, as the rest of the engine's string handling does.
CXXFLAGS += -Wno-class-memaccess -Wno-write-strings
# vendored libretro-common ignores some fread() results; suppress rather than
# patch, so our copy stays diffable against upstream.
CFLAGS   += -Wno-unused-result

# unified core: always the d3xp game module (superset of the base game)
CFLAGS	 +=	-I$(TOPDIR)/d3xp
CXXFLAGS +=     -I$(TOPDIR)/d3xp

CFLAGS     += $(INCFLAGS)
CXXFLAGS   += $(INCFLAGS)

# Objects built with one set of flags (GLES_VER, HAVE_VR, platform, ...) can't
# be linked with another, so every object depends on this record of the
# compile command - a checksum of it - rewritten only when the flags change.
# Through $(shell), not $(file): macOS ships GNU make 3.81, which has no
# $(file) and silently never wrote the stamp.
FLAGS_STAMP := $(CORE_DIR)/.build_flags
BUILD_FLAGS := $(CC) | $(CXX) | $(CFLAGS) | $(CXXFLAGS) | $(fpic)
BUILD_FLAGS_SUM := $(shell printf '%s' '$(subst ','\'',$(BUILD_FLAGS))' | cksum)
ifneq ($(BUILD_FLAGS_SUM),$(shell cat '$(FLAGS_STAMP)' 2>/dev/null))
$(shell printf '%s\n' '$(BUILD_FLAGS_SUM)' > '$(FLAGS_STAMP)')
endif

all: $(TARGET)

$(OBJECTS): $(FLAGS_STAMP)

# Written above while the makefile is read; this only keeps a stamp that
# could not be written from stopping the build.
$(FLAGS_STAMP):
	@printf '%s\n' '$(BUILD_FLAGS_SUM)' > '$@'

$(TARGET): $(OBJECTS)
ifeq ($(STATIC_LINKING), 1)
	$(AR) rcs $@ $(OBJECTS)
else
	$(CXX) $(fpic) $(SHARED) -o $@ $(OBJECTS) $(LDFLAGS) $(GL_LIB) $(LIBS)
endif

# -MMD writes each object's headers to a .d beside it, so a header change
# rebuilds every object that includes it: a class whose layout changes in a
# header, with only some objects rebuilt, is memory corruption.
%.o: $(CORE_DIR)/%.cpp
	$(CXX) $(CFLAGS) $(CXXFLAGS) $(fpic) -MMD -MP -c -o $@ $<

%.o: $(CORE_DIR)/%.c
	$(CC) $(CFLAGS) -std=gnu11 $(fpic) -MMD -MP -c -o $@ $<

-include $(OBJECTS:.o=.d)

clean:
	rm -f $(OBJECTS) $(OBJECTS:.o=.d) $(TARGET) $(FLAGS_STAMP)

.PHONY: clean

print-%:
	@echo '$*=$($*)'
