From 29446dbc5151203def7de3eb48c26b3be6b05d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trung=20L=C3=AA?= <8@tle.id.au> Date: Fri, 27 Mar 2026 14:15:35 +1100 Subject: [PATCH 01/19] Add ppc64le/ppc64 architecture support Add build support for ppc64le (little-endian, POWER8+) and ppc64 (big-endian, POWER5+) architectures: - CMake architecture detection, compiler flags (-mcpu), and NaCl build defines for ppc64el/ppc64 - NACL_RUNTIME_ARCH allowlist and DAEMON_NACL_RUNTIME_ENABLED define to track whether a NaCl loader exists for the target architecture - vm.nacl.available ROM cvar so the UI can detect NaCl availability - Sys::Error when attempting to launch a NaCl VM on a platform without a NaCl loader, advising to use native DLL mode with devmap --- cmake/DaemonArchitecture.cmake | 7 +++++++ cmake/DaemonFlags.cmake | 17 +++++++++++++++++ cmake/DaemonNacl.cmake | 5 +++++ src/engine/framework/VirtualMachine.cpp | 19 +++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/cmake/DaemonArchitecture.cmake b/cmake/DaemonArchitecture.cmake index cbf0033b0b..414aa775d0 100644 --- a/cmake/DaemonArchitecture.cmake +++ b/cmake/DaemonArchitecture.cmake @@ -91,6 +91,12 @@ endif() daemon_add_buildinfo("char*" "DAEMON_NACL_ARCH_STRING" "\"${NACL_ARCH}\"") +# NaCl runtime is only available on architectures that have a NaCl loader. +set(NACL_RUNTIME_ARCH amd64 i686 armhf) +if (NACL_ARCH IN_LIST NACL_RUNTIME_ARCH) + add_definitions(-DDAEMON_NACL_RUNTIME_ENABLED) +endif() + option(USE_ARCH_INTRINSICS "Enable custom code using intrinsics functions or asm declarations" ON) mark_as_advanced(USE_ARCH_INTRINSICS) @@ -111,6 +117,7 @@ set_arch_intrinsics(${ARCH}) set(amd64_PARENT "i686") set(arm64_PARENT "armhf") +set(ppc64el_PARENT "ppc64") if (${ARCH}_PARENT) set_arch_intrinsics(${${ARCH}_PARENT}) diff --git a/cmake/DaemonFlags.cmake b/cmake/DaemonFlags.cmake index 0170401998..53c733b555 100644 --- a/cmake/DaemonFlags.cmake +++ b/cmake/DaemonFlags.cmake @@ -659,6 +659,18 @@ elseif (NOT NACL) set(GCC_GENERIC_ARCH "armv6") # There is no generic tuning option for armv6. unset(GCC_GENERIC_TUNE) + elseif (ARCH STREQUAL "ppc64el") + # POWER8 minimum (first little-endian POWER). + # GCC uses -mcpu instead of -march/-mtune for POWER. + unset(GCC_GENERIC_ARCH) + unset(GCC_GENERIC_TUNE) + set(GCC_GENERIC_CPU "power8") + elseif (ARCH STREQUAL "ppc64") + # POWER5 minimum (first 64-bit POWER in wide use). + # GCC uses -mcpu instead of -march/-mtune for POWER. + unset(GCC_GENERIC_ARCH) + unset(GCC_GENERIC_TUNE) + set(GCC_GENERIC_CPU "power5") else() message(WARNING "Unknown architecture ${ARCH}") endif() @@ -676,6 +688,11 @@ elseif (NOT NACL) if (GCC_GENERIC_TUNE) try_c_cxx_flag_werror(MTUNE "-mtune=${GCC_GENERIC_TUNE}") endif() + + # POWER architectures use -mcpu instead of -march/-mtune. + if (GCC_GENERIC_CPU) + try_c_cxx_flag_werror(MCPU "-mcpu=${GCC_GENERIC_CPU}") + endif() endif() if (USE_CPU_RECOMMENDED_FEATURES) diff --git a/cmake/DaemonNacl.cmake b/cmake/DaemonNacl.cmake index f3310514ee..bfdded2638 100644 --- a/cmake/DaemonNacl.cmake +++ b/cmake/DaemonNacl.cmake @@ -71,6 +71,11 @@ else() add_definitions( -DNACL_BUILD_SUBARCH=32 ) elseif( NACL_ARCH STREQUAL "armhf" ) add_definitions( -DNACL_BUILD_ARCH=arm ) + elseif( NACL_ARCH STREQUAL "ppc64el" OR NACL_ARCH STREQUAL "ppc64" ) + # NaCl does not support PPC, but these defines must be set for native + # builds. Use dummy x86 values as PNaCl does for arch-independent builds. + add_definitions( -DNACL_BUILD_ARCH=x86 ) + add_definitions( -DNACL_BUILD_SUBARCH=64 ) else() message(WARNING "Unknown architecture ${NACL_ARCH}") endif() diff --git a/src/engine/framework/VirtualMachine.cpp b/src/engine/framework/VirtualMachine.cpp index 482c6336d5..0051b56a8d 100644 --- a/src/engine/framework/VirtualMachine.cpp +++ b/src/engine/framework/VirtualMachine.cpp @@ -88,6 +88,18 @@ static Cvar::Cvar vm_timeout( "Receive timeout in seconds", Cvar::NONE, 2); +#if defined(DAEMON_NACL_RUNTIME_ENABLED) +static Cvar::Cvar vm_nacl_available( + "vm.nacl.available", + "Whether NaCl runtime is available on this platform", + Cvar::ROM, true); +#else +static Cvar::Cvar vm_nacl_available( + "vm.nacl.available", + "Whether NaCl runtime is available on this platform", + Cvar::ROM, false); +#endif + namespace VM { // https://github.com/Unvanquished/Unvanquished/issues/944#issuecomment-744454772 @@ -497,6 +509,13 @@ void VMBase::Create() std::pair pair = IPC::Socket::CreatePair(); IPC::Socket rootSocket; +#if !defined(DAEMON_NACL_RUNTIME_ENABLED) + if (type == TYPE_NACL || type == TYPE_NACL_LIBPATH) { + Sys::Error("NaCl VM is not supported on this platform. " + "Set vm.cgame.type and vm.sgame.type to 3 (native DLL) " + "and use devmap instead of map."); + } +#endif if (type == TYPE_NACL || type == TYPE_NACL_LIBPATH) { std::tie(processHandle, rootSocket) = CreateNaClVM(std::move(pair), name, params.debug.Get(), type == TYPE_NACL, params.debugLoader.Get()); } else if (type == TYPE_NATIVE_EXE) { From 2e5b685b1f87913c22c678e92ca469dbf4f0ee93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trung=20L=C3=AA?= <8@tle.id.au> Date: Fri, 27 Mar 2026 13:44:02 +1100 Subject: [PATCH 02/19] Box64 hybrid NaCl emulation for unsupported platforms Run x86_64 NaCl loader and amd64 .nexe game modules under Box64 emulation on platforms without native NaCl support. The native engine communicates with the emulated NaCl sandbox processes via architecture-independent Unix domain socket IPC, requiring no changes to game modules. - Add opt-in DAEMON_NACL_BOX64_EMULATION CMake option (OFF by default, Linux only) that remaps NACL_ARCH to amd64 - Replace architecture-specific NaCl dummy defines with a generic fallback for any unsupported architecture - Prepend box64 to NaCl loader args with PATH resolution - Skip bootstrap helper (incompatible with Box64 double-exec) - Disable platform qualification for emulated loader - Add cvars: vm.box64.path, workaround.box64.disableBootstrap, workaround.box64.disableQualification --- cmake/DaemonArchitecture.cmake | 11 +++ cmake/DaemonNacl.cmake | 9 +- src/engine/framework/VirtualMachine.cpp | 111 +++++++++++++++++++++++- 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/cmake/DaemonArchitecture.cmake b/cmake/DaemonArchitecture.cmake index 414aa775d0..73962ac408 100644 --- a/cmake/DaemonArchitecture.cmake +++ b/cmake/DaemonArchitecture.cmake @@ -81,6 +81,17 @@ if (LINUX OR FREEBSD) # The nexe is system agnostic so there should be no difference with armel. set(NACL_ARCH "armhf") endif() + + set(BOX64_USAGE ppc64el riscv64) + if (ARCH IN_LIST BOX64_USAGE) + option(DAEMON_NACL_BOX64_EMULATION "Use Box64 to emulate x86_64 NaCl loader on unsupported platforms" ON) + if (DAEMON_NACL_BOX64_EMULATION) + # Use Box64 to run x86_64 NaCl loader and amd64 nexe. + # Box64 must be installed and available in PATH at runtime. + set(NACL_ARCH "amd64") + add_definitions(-DDAEMON_NACL_BOX64_EMULATION) + endif() + endif() elseif(APPLE) if ("${ARCH}" STREQUAL arm64) # You can get emulated NaCl going like this: diff --git a/cmake/DaemonNacl.cmake b/cmake/DaemonNacl.cmake index bfdded2638..a763cddb82 100644 --- a/cmake/DaemonNacl.cmake +++ b/cmake/DaemonNacl.cmake @@ -71,13 +71,12 @@ else() add_definitions( -DNACL_BUILD_SUBARCH=32 ) elseif( NACL_ARCH STREQUAL "armhf" ) add_definitions( -DNACL_BUILD_ARCH=arm ) - elseif( NACL_ARCH STREQUAL "ppc64el" OR NACL_ARCH STREQUAL "ppc64" ) - # NaCl does not support PPC, but these defines must be set for native - # builds. Use dummy x86 values as PNaCl does for arch-independent builds. + else() + # NaCl does not support this architecture natively, but these defines must + # be set because nacl_config.h is included unconditionally. Use dummy x86 + # values as PNaCl does for architecture-independent builds. add_definitions( -DNACL_BUILD_ARCH=x86 ) add_definitions( -DNACL_BUILD_SUBARCH=64 ) - else() - message(WARNING "Unknown architecture ${NACL_ARCH}") endif() endif() diff --git a/src/engine/framework/VirtualMachine.cpp b/src/engine/framework/VirtualMachine.cpp index 0051b56a8d..bc37adae72 100644 --- a/src/engine/framework/VirtualMachine.cpp +++ b/src/engine/framework/VirtualMachine.cpp @@ -42,6 +42,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +// POSIX: environ is the process environment, not always declared in headers. +extern char **environ; #ifdef __linux__ #include #if defined(DAEMON_ARCH_armhf) @@ -73,6 +75,54 @@ static Cvar::Cvar workaround_naclSystem_freebsd_disableQualification( "Disable platform qualification when running Linux NaCl loader on FreeBSD through Linuxulator", Cvar::NONE, true); +#if defined(DAEMON_NACL_BOX64_EMULATION) +static Cvar::Cvar workaround_box64_disableQualification( + "workaround.box64.disableQualification", + "Disable platform qualification when running amd64 NaCl loader under Box64 emulation", + Cvar::NONE, true); + +static Cvar::Cvar workaround_box64_disableBootstrap( + "workaround.box64.disableBootstrap", + "Disable NaCl bootstrap helper when using Box64 emulation", + Cvar::NONE, true); + +static Cvar::Cvar vm_box64_path( + "vm.box64.path", + "Path to the box64 binary for NaCl emulation (empty = search PATH)", + Cvar::NONE, ""); + +// Resolve box64 binary path by searching PATH if not explicitly set. +static std::string ResolveBox64Path() { + std::string path = vm_box64_path.Get(); + if (!path.empty()) { + return path; + } + + const char* envPath = getenv("PATH"); + if (!envPath) { + Sys::Error("Box64 emulation is enabled but PATH is not set and vm.box64.path is empty."); + } + + std::string pathStr(envPath); + size_t start = 0; + while (start < pathStr.size()) { + size_t end = pathStr.find(':', start); + if (end == std::string::npos) { + end = pathStr.size(); + } + std::string candidate = pathStr.substr(start, end - start) + "/box64"; + if (access(candidate.c_str(), X_OK) == 0) { + return candidate; + } + start = end + 1; + } + + Sys::Error("Box64 emulation is enabled but 'box64' was not found in PATH. " + "Install Box64 or set vm.box64.path to the full path of the box64 binary."); + return ""; // unreachable +} +#endif + static Cvar::Cvar vm_nacl_qualification( "vm.nacl.qualification", "Enable NaCl loader platform qualification", @@ -128,7 +178,7 @@ static void CheckMinAddressSysctlTooLarge() } // Platform-specific code to load a module -static std::pair InternalLoadModule(std::pair pair, const char* const* args, bool reserve_mem, FS::File stderrRedirect = FS::File()) +static std::pair InternalLoadModule(std::pair pair, const char* const* args, bool reserve_mem, FS::File stderrRedirect = FS::File(), bool inheritEnvironment = false) { #ifdef _WIN32 // Inherit the socket in the child process @@ -213,6 +263,7 @@ static std::pair InternalLoadModule(std::pair InternalLoadModule(std::pair(args), nullptr); + // By default, the child process gets an empty environment for sandboxing. + // When Box64 emulation is used, the child needs to inherit the parent's + // environment so Box64 can find its configuration (e.g. ~/.box64rc, HOME) + // and honor settings like BOX64_DYNAREC_PERFMAP. + char* emptyEnv[] = {nullptr}; + char** envp = inheritEnvironment ? environ : emptyEnv; + int err = posix_spawn(&pid, args[0], &fileActions, nullptr, const_cast(args), envp); posix_spawn_file_actions_destroy(&fileActions); if (err != 0) { Sys::Drop("VM: Failed to spawn process: %s", strerror(err)); @@ -255,6 +312,10 @@ static std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNativeVM(std::pair pair, Str::StringRef name, bool debug) { From 85f358491456b98096f9cc84c49cccfd65ab1a0c Mon Sep 17 00:00:00 2001 From: DolceTriade Date: Fri, 3 Apr 2026 21:28:07 -0700 Subject: [PATCH 03/19] lerptag: Ensure we never return uninitialized data If for any reason, we fail to find a tag for iqm/md5 code, if the attachment is uninitialized like in the entity cache code, we will return NaNs. Fixes this crash for me: ``` tuple=...) at /mnt/media/code/unv-master/daemon/src/common/Util.h:136 at /mnt/media/code/unv-master/daemon/src/common/Util.h:141 ``` --- src/engine/renderer/tr_model.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine/renderer/tr_model.cpp b/src/engine/renderer/tr_model.cpp index 43d10b437c..9df6a84346 100644 --- a/src/engine/renderer/tr_model.cpp +++ b/src/engine/renderer/tr_model.cpp @@ -442,6 +442,9 @@ int RE_LerpTagET( orientation_t* tag, const trRefEntity_t* ent, const char* tagN float frontLerp = frac; float backLerp = 1.0f - frac; + AxisClear( tag->axis ); + VectorClear( tag->origin ); + if ( model->type == modtype_t::MOD_MD5 || model->type == modtype_t::MOD_IQM ) { vec3_t tmp; @@ -476,8 +479,6 @@ int RE_LerpTagET( orientation_t* tag, const trRefEntity_t* ent, const char* tagN if ( !start || !end ) { - AxisClear( tag->axis ); - VectorClear( tag->origin ); return -1; } From 6bf8baa5f56dd70143d36797519fdfc8f8f9f491 Mon Sep 17 00:00:00 2001 From: slipher Date: Wed, 25 Mar 2026 23:36:53 -0500 Subject: [PATCH 04/19] Remove version number from default server name This is truly unnecessary now that we have versions in the server list. Fixes https://github.com/Unvanquished/Unvanquished/issues/1540 --- src/common/Defs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Defs.h b/src/common/Defs.h index 1c2fd6f0d3..b672b20b42 100644 --- a/src/common/Defs.h +++ b/src/common/Defs.h @@ -64,7 +64,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define TEAMCONFIG_NAME "teamconfig.cfg" #define UNNAMED_PLAYER "UnnamedPlayer" -#define UNNAMED_SERVER PRODUCT_NAME " " PRODUCT_VERSION " Server" +#define UNNAMED_SERVER "Unnamed " PRODUCT_NAME " Server" /** file containing our RSA public and private keys */ #define RSAKEY_FILE "pubkey" From c004f239cd3243f58dc9c7b8faa95147fe5f6e8e Mon Sep 17 00:00:00 2001 From: Antoine Fontaine Date: Wed, 25 Mar 2026 17:42:56 +0100 Subject: [PATCH 05/19] Make error message slightly more clear --- cmake/DaemonCBSE.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/DaemonCBSE.cmake b/cmake/DaemonCBSE.cmake index 6cfdbbbc8c..66b50564a1 100644 --- a/cmake/DaemonCBSE.cmake +++ b/cmake/DaemonCBSE.cmake @@ -45,7 +45,7 @@ function(CBSE target definition output) COMMAND ${DAEMON_CBSE_PYTHON_PATH} -c "import jinja2, yaml, collections, argparse, sys, os.path, re" RESULT_VARIABLE RET) if (NOT RET EQUAL 0) - message(FATAL_ERROR "Missing dependences for CBSE generation. Please ensure you have python ≥ 2, python-yaml, and python-jinja installed. + message(FATAL_ERROR "Missing dependences for CBSE generation. Please ensure you have python with python-yaml and python-jinja installed. Use pip install -r src/utils/cbse/requirements.txt to install") endif() set(GENERATED_CBSE ${output}/backend/CBSEBackend.cpp From 8e991aeef043b963098d6697146baf9e025fac50 Mon Sep 17 00:00:00 2001 From: Antoine Fontaine Date: Wed, 25 Mar 2026 17:23:58 +0100 Subject: [PATCH 06/19] Print used SDL path and version --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e4f1825f0..bbe9286d94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -689,6 +689,7 @@ if (BUILD_CLIENT OR WIN32) endif() find_package(SDL3 REQUIRED CONFIG) + message("Found SDL3 ${SDL3_VERSION}: ${SDL3_DIR}") if (WIN32) set(LIBS_ENGINE_BASE ${LIBS_ENGINE_BASE} SDL3::SDL3) From c8fa4aed45bf47bd9974becfa8cf39b3f04175fd Mon Sep 17 00:00:00 2001 From: slipher Date: Wed, 8 Apr 2026 19:13:25 -0300 Subject: [PATCH 07/19] Discard old server commands upon new gamestate Fixes https://github.com/Unvanquished/Unvanquished/issues/1102. This fixes a bug where the configstring state could be wrong due to `cs` server commands being executed out of order with respect to game state processing. Server commands are only processed when the cgame requests a snapshot, so commands received before the client loads the current map which were sent before the gamestate could be processed after the gamestate. Of course there are other commands besides setting configstrings. In general, processing a command from a previous game seems very likely to be a bad idea and unlikely to be good. --- src/engine/client/cl_parse.cpp | 3 +++ src/engine/server/sv_client.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/src/engine/client/cl_parse.cpp b/src/engine/client/cl_parse.cpp index cf91714f19..2169c114fd 100644 --- a/src/engine/client/cl_parse.cpp +++ b/src/engine/client/cl_parse.cpp @@ -409,6 +409,9 @@ void CL_ParseGamestate( msg_t *msg ) // a gamestate always marks a server command sequence clc.serverCommandSequence = MSG_ReadLong( msg ); + + // trash any commands from previous game + clc.lastExecutedServerCommand = clc.serverCommandSequence; } // parse all the configstrings and baselines diff --git a/src/engine/server/sv_client.cpp b/src/engine/server/sv_client.cpp index 3b6f336754..469c99f96a 100644 --- a/src/engine/server/sv_client.cpp +++ b/src/engine/server/sv_client.cpp @@ -354,6 +354,7 @@ void SV_SendClientGameState( client_t *client ) // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side + // TODO(0.57): remove. The client will just throw away old commands on getting a gamestate SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate From ac57ed3af119db98d12f30e3a0f5b581d10796c3 Mon Sep 17 00:00:00 2001 From: slipher Date: Tue, 21 Apr 2026 04:22:36 -0500 Subject: [PATCH 08/19] Don't include RefAPI.h from gamelogic It is a file for internal renderer APIs. --- src/engine/client/cg_msgdef.h | 2 +- src/shared/client/cg_api.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/client/cg_msgdef.h b/src/engine/client/cg_msgdef.h index 2fa99c8a98..9774509d65 100644 --- a/src/engine/client/cg_msgdef.h +++ b/src/engine/client/cg_msgdef.h @@ -24,7 +24,7 @@ along with this program. If not, see . #define CG_MSGDEF_H #include "cg_api.h" -#include "engine/RefAPI.h" +#include "engine/renderer/tr_types.h" #include "common/IPC/CommonSyscalls.h" #include "common/KeyIdentification.h" diff --git a/src/shared/client/cg_api.h b/src/shared/client/cg_api.h index 69a2087035..67c1e3e9bf 100644 --- a/src/shared/client/cg_api.h +++ b/src/shared/client/cg_api.h @@ -33,8 +33,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define SHARED_CLIENT_API_H_ #include "engine/qcommon/q_shared.h" -#include "engine/RefAPI.h" #include "engine/client/cg_api.h" +#include "engine/renderer/tr_types.h" #include "common/KeyIdentification.h" #include "shared/CommonProxies.h" #include From c121f2f3c9d367a623922878b8faf4a630545405 Mon Sep 17 00:00:00 2001 From: slipher Date: Tue, 21 Apr 2026 04:33:22 -0500 Subject: [PATCH 09/19] Add include guards to headers lacking --- src/common/cm/cm_local.h | 5 +++++ src/common/cm/cm_patch.h | 5 +++++ src/common/cm/cm_public.h | 5 +++++ src/engine/qcommon/SurfaceFlags.h | 5 +++++ src/engine/renderer/ShadeCommon.h | 5 +++++ src/engine/server/sg_msgdef.h | 5 +++++ src/engine/sys/con_common.h | 5 +++++ 7 files changed, 35 insertions(+) diff --git a/src/common/cm/cm_local.h b/src/common/cm/cm_local.h index 09ff99311d..4f0d21902e 100644 --- a/src/common/cm/cm_local.h +++ b/src/common/cm/cm_local.h @@ -32,6 +32,9 @@ Maryland 20850 USA. =========================================================================== */ +#ifndef COMMON_CM_CM_LOCAL_H_ +#define COMMON_CM_CM_LOCAL_H_ + #include "cm_public.h" #include "cm_polylib.h" @@ -313,3 +316,5 @@ bool CM_BoundsIntersect( const vec3_t mins, const vec3_t m bool CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point ); // XreaL END + +#endif // COMMON_CM_CM_LOCAL_H_ diff --git a/src/common/cm/cm_patch.h b/src/common/cm/cm_patch.h index 09f4237912..5bc9fd50fd 100644 --- a/src/common/cm/cm_patch.h +++ b/src/common/cm/cm_patch.h @@ -32,6 +32,9 @@ Maryland 20850 USA. =========================================================================== */ +#ifndef COMMON_CM_CM_PATCH_H_ +#define COMMON_CM_CM_PATCH_H_ + //#define CULL_BBOX /* @@ -86,3 +89,5 @@ void CM_SetGridWrapWidth( cGrid_t *grid ); void CM_SubdivideGridColumns( cGrid_t *grid ); void CM_RemoveDegenerateColumns( cGrid_t *grid ); void CM_TransposeGrid( cGrid_t *grid ); + +#endif // COMMON_CM_CM_PATCH_H_ diff --git a/src/common/cm/cm_public.h b/src/common/cm/cm_public.h index 1747445cca..5b26323407 100644 --- a/src/common/cm/cm_public.h +++ b/src/common/cm/cm_public.h @@ -32,6 +32,9 @@ Maryland 20850 USA. =========================================================================== */ +#ifndef COMMON_CM_CM_PUBLIC_H_ +#define COMMON_CM_CM_PUBLIC_H_ + #include "engine/qcommon/q_shared.h" void CM_LoadMap(Str::StringRef name); @@ -79,3 +82,5 @@ int CM_WriteAreaBits( byte *buffer, int area ); // cm_marks.c int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +#endif // COMMON_CM_CM_PUBLIC_H_ diff --git a/src/engine/qcommon/SurfaceFlags.h b/src/engine/qcommon/SurfaceFlags.h index e227c0bbca..4aa8316436 100644 --- a/src/engine/qcommon/SurfaceFlags.h +++ b/src/engine/qcommon/SurfaceFlags.h @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =========================================================================== */ +#ifndef ENGINE_QCOMMON_SURFACEFLAGS_H_ +#define ENGINE_QCOMMON_SURFACEFLAGS_H_ + // this file is used by both engine and game code // see engine/qcommon/q_shared.h @@ -114,3 +117,5 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Jedi Knights games (see OpenJK) also define a third bitfield for flags with MATERIAL prefix to tell surface is water, snow, sand, glass, short grasss, long grass, etc. // Jedi Knights games also redefine a lot of CONTENTS flags (introducing things like CONTENTS_LADDER) and SURF FLAGS (moving SURF_SKY to BIT(13) for example) // Smokin'Guns uses a special .tex sidecar files to tweak surfaces flags + +#endif // ENGINE_QCOMMON_SURFACEFLAGS_H_ diff --git a/src/engine/renderer/ShadeCommon.h b/src/engine/renderer/ShadeCommon.h index da4cac0fcd..0eca1b1c10 100644 --- a/src/engine/renderer/ShadeCommon.h +++ b/src/engine/renderer/ShadeCommon.h @@ -32,6 +32,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =========================================================================== */ +#ifndef ENGINE_RENDERER_SHADECOMMON_H_ +#define ENGINE_RENDERER_SHADECOMMON_H_ + inline size_t GetLightMapNum( const shaderCommands_t* tess ) { return tess->lightmapNum; @@ -276,3 +279,5 @@ inline uint GetShaderProfilerRenderSubGroupsMode( const uint32_t stateBits ) { return 0; } + +#endif // ENGINE_RENDERER_SHADECOMMON_H_ diff --git a/src/engine/server/sg_msgdef.h b/src/engine/server/sg_msgdef.h index 5ace84f369..70a9881474 100644 --- a/src/engine/server/sg_msgdef.h +++ b/src/engine/server/sg_msgdef.h @@ -20,6 +20,9 @@ along with this program. If not, see . =========================================================================== */ +#ifndef ENGINE_SERVER_SG_MSGDEF_H_ +#define ENGINE_SERVER_SG_MSGDEF_H_ + #include "common/IPC/CommonSyscalls.h" // game-module-to-engine calls @@ -196,3 +199,5 @@ using GameClientThinkMsg = IPC::SyncMessage< using GameRunFrameMsg = IPC::SyncMessage< IPC::Message, int> >; + +#endif // ENGINE_SERVER_SG_MSGDEF_H_ diff --git a/src/engine/sys/con_common.h b/src/engine/sys/con_common.h index 1682171f2e..ba0123ed44 100644 --- a/src/engine/sys/con_common.h +++ b/src/engine/sys/con_common.h @@ -31,6 +31,9 @@ Maryland 20850 USA. =========================================================================== */ +#ifndef ENGINE_SYS_CON_COMMON_H_ +#define ENGINE_SYS_CON_COMMON_H_ + #include "common/Color.h" #include "qcommon/q_shared.h" #include "qcommon/qcommon.h" @@ -48,3 +51,5 @@ namespace Color { int To4bit( const Color& color ) NOEXCEPT; } // namespace Color + +#endif // ENGINE_SYS_CON_COMMON_H_ From 0d65b8a176c55de97356295290aecb70f51beac5 Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 23 Apr 2026 01:24:34 -0500 Subject: [PATCH 10/19] Remove useless trap_R_AddRefEntityToScene return value It returns how many entities have been added to the current scene, but there is nothing useful the cgame can do with this number. All the APIs with entity id require adding the entity to the cache to get an id, not to the scene. --- src/shared/client/cg_api.cpp | 8 +------- src/shared/client/cg_api.h | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/shared/client/cg_api.cpp b/src/shared/client/cg_api.cpp index da4c679f54..c530c58dd6 100644 --- a/src/shared/client/cg_api.cpp +++ b/src/shared/client/cg_api.cpp @@ -295,14 +295,9 @@ void trap_R_ClearScene() cmdBuffer.SendMsg(); } -/* HACK: We need the entityNum to get the correct positions for entities that need to be attached to another entity's bone -This must be equal to the r_numEntities in engine at the time of adding the entity */ -static int entityNum; -int trap_R_AddRefEntityToScene( const refEntity_t *re ) +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { cmdBuffer.SendMsg(*re); - entityNum++; - return entityNum - 1; } void trap_R_SyncRefEntities( const std::vector& ents ) { @@ -380,7 +375,6 @@ void trap_R_AddLightToScene( const vec3_t origin, float radius, float intensity, void trap_R_RenderScene( const refdef_t *fd ) { - entityNum = 0; cmdBuffer.SendMsg(*fd); } diff --git a/src/shared/client/cg_api.h b/src/shared/client/cg_api.h index 67c1e3e9bf..b6bc99796b 100644 --- a/src/shared/client/cg_api.h +++ b/src/shared/client/cg_api.h @@ -65,7 +65,7 @@ qhandle_t trap_R_RegisterModel( const char *name ); qhandle_t trap_R_RegisterSkin( const char *name ); qhandle_t trap_R_RegisterShader( const char *name, int flags ); void trap_R_ClearScene(); -int trap_R_AddRefEntityToScene( const refEntity_t *re ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); void trap_R_SyncRefEntities( const std::vector& ents ); std::vector trap_R_SyncLerpTags( const std::vector& lerpTags ); void trap_R_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts ); From 9ceed9c3a1f36708a2f53d2004b221ba9cd123d5 Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 9 Apr 2026 13:48:14 -0300 Subject: [PATCH 11/19] Drop on impossible server command sequence nums Have the client drop the connection in these cases: - A gamestate that decreases the sequence number. This should be impossible because the netchan guarantees that packets can't be reordered, although they may be dropped. - A server command which a sequence number higher than the next one. This should be impossible because the server re-sends all unacknowledged commands together each time. If the number jumped by more than one the client would end up re-executing old garbage from the buffer. --- src/engine/client/cl_cgame.cpp | 3 ++- src/engine/client/cl_parse.cpp | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/engine/client/cl_cgame.cpp b/src/engine/client/cl_cgame.cpp index d716c90b0b..d4e3b65c1b 100644 --- a/src/engine/client/cl_cgame.cpp +++ b/src/engine/client/cl_cgame.cpp @@ -137,7 +137,8 @@ void CL_ConfigstringModified( Cmd::Args& csCmd ) /* =================== CL_HandleServerCommand -CL_GetServerCommand + +Returns true if the command should be passed to the cgame =================== */ bool CL_HandleServerCommand(Str::StringRef text, std::string& newText) { diff --git a/src/engine/client/cl_parse.cpp b/src/engine/client/cl_parse.cpp index 2169c114fd..45e8bfad66 100644 --- a/src/engine/client/cl_parse.cpp +++ b/src/engine/client/cl_parse.cpp @@ -408,7 +408,14 @@ void CL_ParseGamestate( msg_t *msg ) CL_ClearState(); // a gamestate always marks a server command sequence - clc.serverCommandSequence = MSG_ReadLong( msg ); + int commandNum = MSG_ReadLong( msg ); + + if ( commandNum < clc.serverCommandSequence ) + { + Sys::Drop( "Gamestate moved serverCommandSequence backward" ); + } + + clc.serverCommandSequence = commandNum; // trash any commands from previous game clc.lastExecutedServerCommand = clc.serverCommandSequence; @@ -499,6 +506,13 @@ void CL_ParseCommandString( msg_t *msg ) return; } + if ( clc.serverCommandSequence + 1 != seq ) + { + Sys::Drop( "Out-of-sequence server command: expected %d, got %d", + clc.serverCommandSequence + 1, seq ); + return; + } + clc.serverCommandSequence = seq; index = seq & ( MAX_RELIABLE_COMMANDS - 1 ); From 2af680cc90253c83d1116520268d80a03d948006 Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 9 Apr 2026 18:34:29 -0300 Subject: [PATCH 12/19] CL_FillServerCommand: remove demo special case A special case to allow demos to request bad command ranges is not needed. The gamestate generated when starting the demo sets the serverCommandSequence number appropriately, so the demo replay will be able to request the correct range. --- src/engine/client/cl_cgame.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/engine/client/cl_cgame.cpp b/src/engine/client/cl_cgame.cpp index d4e3b65c1b..51875f0ba4 100644 --- a/src/engine/client/cl_cgame.cpp +++ b/src/engine/client/cl_cgame.cpp @@ -256,13 +256,6 @@ void CL_FillServerCommands(std::vector& commands, int start, int en // if we have irretrievably lost a reliable command, drop the connection if ( start <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { - // when a demo record was started after the client got a whole bunch of - // reliable commands then the client never got those first reliable commands - if ( clc.demoplaying ) - { - return; - } - Sys::Drop( "CL_FillServerCommand: a reliable command was cycled out" ); } From bb785922075cc9b4e63da5fa3c3fe88ebf011393 Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 23 Apr 2026 22:09:10 -0500 Subject: [PATCH 13/19] Remove nonexistent function's declaration --- src/shared/client/cg_api.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared/client/cg_api.h b/src/shared/client/cg_api.h index b6bc99796b..069344358c 100644 --- a/src/shared/client/cg_api.h +++ b/src/shared/client/cg_api.h @@ -82,7 +82,6 @@ void trap_R_ResetClipRegion(); void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); void trap_R_DrawRotatedPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader, float angle ); void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); -int trap_R_LerpTag( orientation_t *tag, const refEntity_t* refent, const char *tagName, int startIndex ); void trap_R_GetTextureSize( qhandle_t handle, int *x, int *y ); qhandle_t trap_R_GenerateTexture( const byte *data, int x, int y ); void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); From d067cb7d9bd6b42e6b3679970a2a31b70e06dc34 Mon Sep 17 00:00:00 2001 From: slipher Date: Mon, 27 Apr 2026 11:36:00 -0500 Subject: [PATCH 14/19] Fix null renderer SyncLerpTags It should return a response with the expected number of items. --- src/engine/null/null_renderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/null/null_renderer.cpp b/src/engine/null/null_renderer.cpp index 5f84d35477..7ee62efc29 100644 --- a/src/engine/null/null_renderer.cpp +++ b/src/engine/null/null_renderer.cpp @@ -80,8 +80,8 @@ void RE_EndRegistration() { } void RE_ClearScene() { } void RE_AddRefEntityToScene( const refEntity_t * ) { } void RE_SyncRefEntities( const std::vector& ) {} -std::vector RE_SyncLerpTags( const std::vector& ) { - return {}; +std::vector RE_SyncLerpTags( const std::vector& in ) { + return std::vector(in.size()); } void RE_AddPolyToScene( qhandle_t, int, const polyVert_t* ) { } void RE_AddPolysToScene( qhandle_t, int, const polyVert_t*, int ) { } From ed330b90427e465a60387196216d53cec722313a Mon Sep 17 00:00:00 2001 From: slipher Date: Mon, 27 Apr 2026 16:28:28 -0500 Subject: [PATCH 15/19] Use bool8_t typedef for IPC booleans --- src/engine/renderer/tr_types.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/engine/renderer/tr_types.h b/src/engine/renderer/tr_types.h index 778a7a64f4..2c90e9bb47 100644 --- a/src/engine/renderer/tr_types.h +++ b/src/engine/renderer/tr_types.h @@ -58,11 +58,11 @@ using bool8_t = uint8_t; // renderfx flags enum RenderFx : uint8_t { - RF_THIRD_PERSON = 0x000001, // don't draw through eyes, only mirrors (player bodies, chat sprites) - RF_FIRST_PERSON = 0x000002, // only draw through eyes (view weapon, damage blood blob) - RF_DEPTHHACK = 0x000004, // for view weapon Z crunching - RF_NOSHADOW = 0x000008, // don't add stencil shadows - RF_SWAPCULL = 0x000010 // swap CT_FRONT_SIDED and CT_BACK_SIDED + RF_THIRD_PERSON = 0x01, // don't draw through eyes, only mirrors (player bodies, chat sprites) + RF_FIRST_PERSON = 0x02, // only draw through eyes (view weapon, damage blood blob) + RF_DEPTHHACK = 0x04, // for view weapon Z crunching + RF_NOSHADOW = 0x08, // don't add stencil shadows + RF_SWAPCULL = 0x10 // swap CT_FRONT_SIDED and CT_BACK_SIDED }; // refdef flags @@ -230,14 +230,14 @@ struct refEntity_t EntityTag positionOnTag; - int8_t clearOrigin; - int8_t clearOrigin2; + bool8_t clearOrigin; + bool8_t clearOrigin2; - int8_t boundsAdd; + bool8_t boundsAdd; - int8_t nonNormalizedAxes; // axis are not normalized, i.e. they have scale + bool8_t nonNormalizedAxes; // axis are not normalized, i.e. they have scale - int8_t active; + bool8_t active; uint16_t attachmentEntity; From 27bc966e9d7d70f44d27454d3eb2d5526bd50dec Mon Sep 17 00:00:00 2001 From: slipher Date: Mon, 27 Apr 2026 16:38:29 -0500 Subject: [PATCH 16/19] Consistently name entity cache-related funcs It could be annoying when following the call hierarchy across the function pointers, if the functions have different name than the function pointers. --- src/engine/renderer/EntityCache.cpp | 4 ++-- src/engine/renderer/EntityCache.h | 4 ++-- src/engine/renderer/tr_init.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/engine/renderer/EntityCache.cpp b/src/engine/renderer/EntityCache.cpp index 9d69487098..06f130e801 100644 --- a/src/engine/renderer/EntityCache.cpp +++ b/src/engine/renderer/EntityCache.cpp @@ -264,7 +264,7 @@ void ClearEntityCache() { } } -std::vector SyncEntityCacheToCGame( const std::vector& lerpTags ) { +std::vector RE_SyncLerpTags( const std::vector& lerpTags ) { std::vector entityOrientations; entityOrientations.reserve( lerpTags.size() ); @@ -284,7 +284,7 @@ std::vector SyncEntityCacheToCGame( const std::vector& ents ) { +void RE_SyncRefEntities( const std::vector& ents ) { for ( const EntityUpdate& ent : ents ) { bool flip = entities[ent.id].e.active != ent.ent.active; diff --git a/src/engine/renderer/EntityCache.h b/src/engine/renderer/EntityCache.h index 9540d8ca2e..135e09b5e7 100644 --- a/src/engine/renderer/EntityCache.h +++ b/src/engine/renderer/EntityCache.h @@ -53,7 +53,7 @@ void TransformEntity( trRefEntity_t* ent ); void ClearEntityCache(); -std::vector SyncEntityCacheToCGame( const std::vector& lerpTags ); -void SyncEntityCacheFromCGame( const std::vector& ents ); +std::vector RE_SyncLerpTags( const std::vector& lerpTags ); +void RE_SyncRefEntities( const std::vector& ents ); #endif // ENTITY_CACHE_H diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index b6174545d7..2777aa8a26 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -1693,8 +1693,8 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p re.ClearScene = RE_ClearScene; re.AddRefEntityToScene = RE_AddRefEntityToScene; - re.SyncRefEntities = SyncEntityCacheFromCGame; - re.SyncLerpTags = SyncEntityCacheToCGame; + re.SyncRefEntities = RE_SyncRefEntities; + re.SyncLerpTags = RE_SyncLerpTags; re.AddPolyToScene = RE_AddPolyToSceneET; re.AddPolysToScene = RE_AddPolysToScene; From cb39538697264b96e734585ba512d1c9d33bd581 Mon Sep 17 00:00:00 2001 From: slipher Date: Wed, 29 Apr 2026 18:45:58 -0500 Subject: [PATCH 17/19] Remove pointless slash normalization in hash functions When names are looked up in hash tables, the equality testing is done with stricmp; there is no special behavior for backslashes. So it is wrong to treat them specially in the hash function: the hashing and equality functions should go hand-in-hand. --- src/engine/renderer/tr_image.cpp | 5 ----- src/engine/renderer/tr_shader.cpp | 12 +----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 3d87ecf326..e0bcd76d00 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -76,11 +76,6 @@ unsigned int GenerateImageHashValue( const char *fname ) { letter = Str::ctolower( fname[ i ] ); - if ( letter == '\\' ) - { - letter = '/'; // damn path names - } - hash += ( unsigned )( letter ) * ( i + 119 ); i++; } diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index bffc8d2f72..0d06255a8b 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -123,17 +123,7 @@ static unsigned int generateHashValue( const char *fname, const int size ) if ( letter == '.' ) { - break; // don't include extension - } - - if ( letter == '\\' ) - { - letter = '/'; // damn path names - } - - if ( letter == PATH_SEP ) - { - letter = '/'; // damn path names + break; // don't include extension. FIXME: could have multiple dots } hash += ( unsigned )( letter ) * ( i + 119 ); From e80ec0862735e595b8e61dcbf0caea7d645ed345 Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 23 Apr 2026 22:08:54 -0500 Subject: [PATCH 18/19] Allow backslashes, extra slashes in shader/image names Remove initial slashes or multiple consecutive slashes and convert \ to / when looking up shaders or images. Useful for Smokin' Guns assets. --- src/common/FileSystem.cpp | 21 +++++++++++++++++++++ src/common/FileSystem.h | 5 +++++ src/engine/renderer/tr_image.cpp | 14 ++++++++------ src/engine/renderer/tr_shader.cpp | 3 ++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/common/FileSystem.cpp b/src/common/FileSystem.cpp index 45bd66b8b0..abed38d07f 100644 --- a/src/common/FileSystem.cpp +++ b/src/common/FileSystem.cpp @@ -477,6 +477,27 @@ std::string Build(Str::StringRef base, Str::StringRef path) return out; } +std::string NormalizeSlashes(Str::StringRef path) +{ + std::string out; + out.reserve(path.size()); + bool lastSlash = true; + + for (char c : path) { + if (c == '/' || c == '\\') { + if (!lastSlash) { + lastSlash = true; + out.push_back('/'); + } + } else { + out.push_back( c ); + lastSlash = false; + } + } + + return out; +} + std::string DirName(Str::StringRef path) { if (path.empty()) diff --git a/src/common/FileSystem.h b/src/common/FileSystem.h index 725afa66d8..787e95bb43 100644 --- a/src/common/FileSystem.h +++ b/src/common/FileSystem.h @@ -177,6 +177,11 @@ namespace Path { // Build a path from components std::string Build(Str::StringRef base, Str::StringRef path); + // Replace \ with / + // Remove multiple consecutive slashes + // Remove initial slashes + std::string NormalizeSlashes(Str::StringRef path); + // Get the directory portion of a path: // a/b/c => a/b // a/b/ => a diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index e0bcd76d00..9330290c7c 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -1818,19 +1818,21 @@ Finds or loads the given image. Returns nullptr if it fails, not a default image. ============== */ -image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) +image_t *R_FindImageFile( const char *imageName0, imageParams_t &imageParams ) { - if ( !imageName ) + if ( !imageName0 ) { return nullptr; } - unsigned hash = GenerateImageHashValue( imageName ); + std::string imageName = FS::Path::NormalizeSlashes( imageName0 ); + + unsigned hash = GenerateImageHashValue( imageName.c_str() ); // See if the image is already loaded. for ( image_t *image = r_imageHashTable[ hash ]; image; image = image->next ) { - if ( !Q_strnicmp( imageName, image->name, sizeof( image->name ) ) ) + if ( Str::IsIEqual( imageName, image->name ) ) { if ( imageParams == image->initialParams || r_allowImageParamMismatch.Get() ) { @@ -1889,7 +1891,7 @@ image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) byte *pic[ MAX_TEXTURE_MIPS * MAX_TEXTURE_LAYERS ]; pic[ 0 ] = nullptr; - R_LoadImage( imageName, pic, &width, &height, &numLayers, &numMips, &imageParams.bits ); + R_LoadImage( imageName.c_str(), pic, &width, &height, &numLayers, &numMips, &imageParams.bits); if ( *pic ) { @@ -1909,7 +1911,7 @@ image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) R_ProcessLightmap( *pic, width, height, imageParams.bits ); } - image_t *image = R_CreateImage( imageName, (const byte **)pic, width, height, numMips, imageParams ); + image_t *image = R_CreateImage( imageName.c_str(), (const byte**)pic, width, height, numMips, imageParams); image->initialParams = initialParams; Z_Free( *pic ); diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index 0d06255a8b..13f9ea981d 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -6237,7 +6237,8 @@ shader_t *R_FindShader( const char *name, int flags ) return tr.defaultShader; } - COM_StripExtension3( name, strippedName, sizeof( strippedName ) ); + COM_StripExtension3( FS::Path::NormalizeSlashes( name ).c_str(), + strippedName, sizeof( strippedName ) ); hash = generateHashValue( strippedName, FILE_HASH_SIZE ); From f2b6520d52f5d986d1dccc9eddcad492438fc583 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 4 Mar 2026 10:54:55 +0100 Subject: [PATCH 19/19] renderer: fix shader dump line numbering - reset line number at GLSL shader start - use custom line number for the GLSL header to avoid line number duplicates - use custom line number for the deform vertex header - reset line count on `#line 0` --- src/engine/renderer/gl_shader.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/engine/renderer/gl_shader.cpp b/src/engine/renderer/gl_shader.cpp index 64b942fae8..22ee8d605b 100644 --- a/src/engine/renderer/gl_shader.cpp +++ b/src/engine/renderer/gl_shader.cpp @@ -490,10 +490,14 @@ static void AddConst( std::string& str, const std::string& name, float v1, float static std::string GenVersionDeclaration( const std::vector &addedExtensions ) { // Declare version. - std::string str = Str::Format( "#version %d %s\n\n", + std::string str = Str::Format( "#version %d %s\n", glConfig.shadingLanguageVersion, glConfig.shadingLanguageVersion >= 150 ? ( glConfig.glCoreProfile ? "core" : "compatibility" ) : "" ); + str += "#line 1000000000\n"; + + str += "\n"; + // Add supported GLSL extensions. for ( const auto& addedExtension : addedExtensions ) { addExtension( str, addedExtension.available, addedExtension.minGlslVersion, addedExtension.name ); @@ -859,7 +863,10 @@ std::string GLShaderManager::GetDeformShaderName( const int index ) { std::string GLShaderManager::BuildDeformShaderText( const std::string& steps ) { std::string shaderText; - shaderText = steps + "\n"; + shaderText = "\n" + steps + "\n"; + + shaderText += "#line 2000000000\n"; + shaderText += GetShaderText( "deformVertexes_vp.glsl" ); return shaderText; @@ -1223,6 +1230,13 @@ std::string GLShaderManager::ProcessInserts( const std::string& shaderText ) con while ( std::getline( shaderTextStream, line, '\n' ) ) { ++lineCount; + + /* The deform vertex header is prepended to the mainText and is part + of the shaderText, so we should reset line numbering after it. */ + if ( line == "#line 0" ) { + lineCount = 0; + } + const std::string::size_type position = line.find( "#insert" ); if ( position == std::string::npos || line.find_first_not_of( " \t" ) != position ) { out += line + "\n"; @@ -1353,7 +1367,9 @@ void GLShaderManager::InitShader( GLShader* shader ) { if ( shaderType.enabled ) { Com_sprintf( filename, sizeof( filename ), "%s%s.glsl", shaderType.path.c_str(), shaderType.postfix ); - shaderType.mainText = GetShaderText( filename ); + /* The deform vertex header is prepended to the mainText, + so we should reset line numbering after it. */ + shaderType.mainText = "#line 0\n" + GetShaderText( filename ); } }