From 372e77d1cca87ea38f930db4d1beb7ab7b16eb79 Mon Sep 17 00:00:00 2001 From: Erasmo Bellumat Date: Tue, 28 Apr 2026 23:00:05 -0300 Subject: [PATCH 1/4] macOS Apple Silicon port + Vulkan/MoltenVK backend opt-in (WIP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Outer-repo half of the macOS arm64 + Vulkan port. The PsyCross submodule holds the new Vulkan backend itself (separate PR on its own `vulkan-port` branch); this commit tracks that submodule head and provides the build-system, game-side and macOS-portability fixes the backend needs to actually link and run. State of the port: builds clean on macOS arm64 with both renderers; on Apple Silicon the OpenGL path also works but exhibits geometry flicker / dropped primitives because of the GL-on-Metal compatibility layer's relaxed sync, which motivated the Vulkan port. Driver 2 menu / HUD / 3D world geometry render under Vulkan; remaining gaps are blend modes, stencil, scissor, and a few PSX scratch-render paths (GR_StoreFrameBuffer). Build system: - src_rebuild/premake5.lua: --renderer={opengl|vulkan} option, macOS Homebrew prefix detection, Vulkan headers + loader + MoltenVK + shaderc (statically linked for GLSL→SPIR-V at runtime), framework links (Metal / QuartzCore / IOSurface), and exclusion of OpenGL sources when Vulkan is selected. - src_rebuild/premake5_psycross.lua: same wiring on the static-lib side; `removefiles` switches PsyX_render.cpp ↔ PsyX_render_vk.cpp. - src_rebuild/premake_modules/usage/usage.lua: tolerate the modern premake5 `uses` field already existing as a core API (older custom registration would error out on premake5 >= beta8). Game-side fixes (LP64 / arm64): - src_rebuild/Game/driver2.h: `trap()` macro now uses `__builtin_trap()` instead of the x86-only `__asm__("int3")`. - src_rebuild/Game/psyx_compat.h: OTTYPE = OT_TAG also on __aarch64__/__arm64__/_M_ARM64. Without this the OT array allocates 8-byte slots while ClearOTagR/addPrim/DrawOTag treat the array as 12-byte OT_TAG — silent corruption of every OT linked-list pointer. - src_rebuild/Game/dr2locale.{c,h}: malloc.h guard for macOS; GET_GAME_TXT/GET_MISSION_TXT cast pointers via u_intptr first to avoid clang's "cast from pointer to smaller type loses information" error. - src_rebuild/Game/C/civ_ai.c, dr2roads.c, draw.c, event.c, glaunch.c, mission.c, targets.c: same intptr_t-routed casts where the engine packed small integers into pointer-sized fields. - src_rebuild/utils/{fs.{cpp,h}, targa.cpp, audio_source/snd_wav_cache.cpp, video_source/VideoPlayer.cpp}, src_rebuild/tools/font_tool/font_tool_main.cpp: malloc.h guards (macOS uses stdlib.h), `__unix__` guards extended to include `__APPLE__`, GL-only paths in VideoPlayer guarded so the Vulkan build doesn't drag GL symbols. Misc: - BUILD_MACOS.md: build instructions (deps, premake invocation, both renderer options). - data/config.ini: NTSC defaults sized for the new window. - .gitignore: keep the user's CD-ROM images (Driver2CD1/2.bin) out of the repo. --- .gitignore | 12 ++ BUILD_MACOS.md | 167 ++++++++++++++++++ data/config.ini | 4 +- src_rebuild/Game/C/civ_ai.c | 2 +- src_rebuild/Game/C/dr2roads.c | 2 +- src_rebuild/Game/C/draw.c | 4 +- src_rebuild/Game/C/event.c | 2 +- src_rebuild/Game/C/glaunch.c | 8 +- src_rebuild/Game/C/mission.c | 2 +- src_rebuild/Game/C/targets.c | 5 +- src_rebuild/Game/dr2locale.c | 3 + src_rebuild/Game/dr2locale.h | 4 +- src_rebuild/Game/driver2.h | 4 +- src_rebuild/Game/psyx_compat.h | 2 +- src_rebuild/PsyCross | 2 +- src_rebuild/premake5.lua | 137 ++++++++++++-- src_rebuild/premake5_psycross.lua | 51 ++++++ src_rebuild/premake_modules/usage/usage.lua | 15 +- .../tools/font_tool/font_tool_main.cpp | 2 + .../utils/audio_source/snd_wav_cache.cpp | 3 + src_rebuild/utils/fs.cpp | 7 +- src_rebuild/utils/fs.h | 2 +- src_rebuild/utils/targa.cpp | 3 + .../utils/video_source/VideoPlayer.cpp | 4 +- 24 files changed, 404 insertions(+), 43 deletions(-) create mode 100644 BUILD_MACOS.md diff --git a/.gitignore b/.gitignore index ed742dba2..55ab642c9 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,15 @@ src_rebuild/obj/* src_rebuild/project_* src_rebuild/*.make src_rebuild/Makefile + +# Driver 2 game data (CD images + extracted FMV/XA — too large for git) +data/install/*.bin +data/install/*.cue +data/DRIVER2/FMV/ +data/DRIVER2/XA/ +data/REDRIVER2.log + +# Build outputs +src_rebuild/bin/ +src_rebuild/obj/ +src_rebuild/project_gmake_*/ diff --git a/BUILD_MACOS.md b/BUILD_MACOS.md new file mode 100644 index 000000000..bd3d10de6 --- /dev/null +++ b/BUILD_MACOS.md @@ -0,0 +1,167 @@ +# REDRIVER2 — Building on macOS Apple Silicon + +Build port of REDRIVER2 for macOS arm64 (Apple Silicon — M1/M2/M3/M4). +Also works on Intel macs (x86_64) via the same automatic prefix +detection. + +## Prerequisites + +1. **Xcode Command Line Tools** + + ```bash + xcode-select --install + ``` + +2. **Homebrew** — https://brew.sh + +3. **Dependencies via Homebrew** + + OpenGL build (default): + + ```bash + brew install premake sdl2 jpeg openal-soft + ``` + + Vulkan / MoltenVK build (optional): + + ```bash + brew install premake sdl2 jpeg openal-soft \ + vulkan-headers vulkan-loader molten-vk shaderc + ``` + + On Apple Silicon brew installs under `/opt/homebrew`; on Intel macs + it's `/usr/local`. `premake5.lua` detects either automatically. + +## Build + +```bash +git clone https://github.com/OpenDriver2/REDRIVER2.git +cd REDRIVER2 +git submodule update --init --recursive +cd src_rebuild + +# OpenGL backend (default) +premake5 gmake + +# OR: Vulkan / MoltenVK backend +premake5 --renderer=vulkan gmake + +cd project_gmake_macosx + +# Apple Silicon +make config=release_arm64 -j$(sysctl -n hw.ncpu) + +# Intel +make config=release_x64 -j$(sysctl -n hw.ncpu) +``` + +Available configurations: `debug_arm64`, `release_arm64`, +`release_dev_arm64` (and the matching `x64` variants). + +Final binary: `src_rebuild/bin/Release/REDRIVER2`. + +## Running the game + +REDRIVER2 needs the original Driver 2 (PSX) data files alongside the +executable. See the +[installation wiki](https://github.com/OpenDriver2/REDRIVER2/wiki/Installation-instructions) +for the asset extraction steps. + +## Optional environment variables + +To point at non-Homebrew dependency installs: + +```bash +export MAC_SDL2_DIR=/path/to/sdl2 +export MAC_OPENAL_DIR=/path/to/openal-soft +export MAC_JPEG_DIR=/path/to/jpeg +# Vulkan-only: +export MAC_VULKAN_HEADERS=/path/to/vulkan-headers +export MAC_VULKAN_LOADER=/path/to/vulkan-loader +export MAC_MOLTENVK=/path/to/molten-vk +``` + +## What changed for macOS arm64 + +Summary of the deltas vs upstream: + +### Build system +- `src_rebuild/premake5.lua` — `system:macosx` filter with `x64` and + `arm64` platforms, Homebrew `includedirs`/`libdirs`, framework links + (`Cocoa`, `OpenGL` or `Metal`+`QuartzCore`+`IOSurface`+`vulkan`), + rpaths for the brew dylibs. Adds the `--renderer={opengl|vulkan}` + option that gates the OpenGL vs Vulkan source files. +- `src_rebuild/premake5_psycross.lua` — equivalent macOS filter on the + static-lib side, plus `removefiles` to switch `PsyX_render.cpp` ↔ + `PsyX_render_vk.cpp` based on the renderer option. +- `src_rebuild/premake_modules/usage/usage.lua` — guard so the custom + `uses` API doesn't collide with the one premake5 ≥ beta8 ships in + core. + +### arm64 / 64-bit portability +- `src_rebuild/Game/driver2.h` — `trap()` now uses `__builtin_trap()` + instead of the x86-only `__asm__("int3")`. +- `src_rebuild/PsyCross/include/psx/libgpu.h` — `P_LEN = 3` on arm64 + (same value used for x86_64), so the prim-tag struct lays out + identically across all 64-bit targets. +- `src_rebuild/PsyCross/include/psx/types.h` — on LP64 (macOS arm64/x64, + Linux x64) `u_long` and `ulong` map to `uint32_t` so the PSX 32-bit + semantics are preserved. Pre-defines `_U_LONG`/`_U_INT`/`_U_CHAR`/ + `_U_SHORT` on macOS to stop `` from re-declaring those + types as 64-bit. +- `premake5.lua` adds a forced `-include` of `psx/types.h` on the macOS + filter so those guards are seen before any system header. + +### macOS-specific headers +- `` (non-existent on macOS) replaced by `` with a + guard in: + - `src_rebuild/PsyCross/src/psx/LIBCD.C` + - `src_rebuild/utils/fs.cpp` + - `src_rebuild/utils/audio_source/snd_wav_cache.cpp` + - `src_rebuild/utils/targa.cpp` + - `src_rebuild/Game/dr2locale.c` + - `src_rebuild/tools/font_tool/font_tool_main.cpp` +- `LIBCD.C` and `utils/fs.{cpp,h}` — `__unix__` guards extended to + include `__APPLE__` (macOS doesn't define `__unix__`). + +### Pointer→int casts on LP64 +Pointers are 64-bit on arm64/x86_64; `(int)ptr` loses information and +clang treats that as an error. Routed through `intptr_t` / +`u_intptr`: +- `src_rebuild/Game/dr2locale.h` — `GET_GAME_TXT`/`GET_MISSION_TXT` +- `src_rebuild/Game/C/civ_ai.c` — `Random2((int)straight)` +- `src_rebuild/Game/C/dr2roads.c` — `(int)plane & 3` +- `src_rebuild/Game/C/draw.c` — `primtab`/`primptr` checks +- `src_rebuild/Game/C/event.c` — `FIXEDH((int)ev->node * RSIN(...))` +- `src_rebuild/Game/C/glaunch.c` — `(int)param` in several callbacks +- `src_rebuild/Game/C/mission.c` — `(u_int)((char*)...)` + +### Stricter C++ checks +- `src_rebuild/PsyCross/src/psx/LIBETC.C` — pointer→int cast routed + via `intptr_t` (`ResetCallback`/`VSyncCallback`). +- `src_rebuild/PsyCross/src/psx/LIBGPU.C` — explicit `(DR_TPAGE*)opri` + cast. +- `src_rebuild/PsyCross/src/psx/LIBSPU.C` — added `psx/libetc.h` + include for `ResetCallback()`. + +### Missing GTE function +- `src_rebuild/Game/C/targets.c:327` — `RotTransPers()` is declared but + not defined inside PsyCross. Replaced with the equivalent + `gte_RotTransPers()` macro and the otz output captured locally. + +## Status + +- ✅ Clean build on arm64 (Apple Silicon). +- ✅ Native Mach-O arm64 binary (`file` confirms `arm64`). +- ⚠️ macOS OpenGL has been *deprecated* since 10.14 but the legacy + driver still works up to OpenGL 4.1. PsyCross uses GL 2.x so it + runs, though Apple's GL-on-Metal compatibility layer has subtly + different sync behaviour and produces flicker / dropped primitives + on some scenes. +- ⚠️ The Vulkan / MoltenVK backend (opt-in via `--renderer=vulkan`) + side-steps the GL-on-Metal issues. Currently a work-in-progress — + Driver 2 main menu, HUD and 3D world geometry render; some passes + (blend modes, stencil, scissor, framebuffer-back-to-VRAM) are still + in flight. +- ⚠️ A full playtest hasn't been run end-to-end — the game requires + the original Driver 2 assets to launch. diff --git a/data/config.ini b/data/config.ini index 1ae3f6a91..31f4bb77e 100644 --- a/data/config.ini +++ b/data/config.ini @@ -81,7 +81,7 @@ pad1device=-1 # player 1 controller device; -1 for automatic assignment pad2device=-1 # player 2 controller device; -1 for automatic assignment [render] -windowWidth=1280 +windowWidth=960 windowHeight=720 fullscreen=0 # enable full screen mode; it takes screen resolution pgxpTextureMapping=1 @@ -97,7 +97,7 @@ fieldOfView=256 # 128..384, 256 is default disableChicagoBridges=0 # Experimental: also activate AI roads freeCamera=1 # Press F7 in game to enable fastLoadingScreens=1 -widescreenOverlays=1 # set 1 to see map, bars and stats aligned to screen corners +widescreenOverlays=0 # set 1 to see map, bars and stats aligned to screen corners driver1music=0 # put Driver 1's MUSIC.BIN as D1MUSIC.BIN to DRIVER2\SOUND folder overrideContent=0 # this enables texture and car model modding userChases=RacingFreak,Snoopi,Olanov,Vortex,Fireboyd78 \ No newline at end of file diff --git a/src_rebuild/Game/C/civ_ai.c b/src_rebuild/Game/C/civ_ai.c index fd6822f2e..0452f24e1 100644 --- a/src_rebuild/Game/C/civ_ai.c +++ b/src_rebuild/Game/C/civ_ai.c @@ -1083,7 +1083,7 @@ int CheckChangeLanes(DRIVER2_STRAIGHT* straight, DRIVER2_CURVE* curve, int distA segLen = straight->length; } - newLane = currentLane + (Random2((int)straight) >> 7 & 2U) + 0xff & 0xff; + newLane = currentLane + (Random2((int)(intptr_t)straight) >> 7 & 2U) + 0xff & 0xff; if (tryToPark) { diff --git a/src_rebuild/Game/C/dr2roads.c b/src_rebuild/Game/C/dr2roads.c index bade3a62b..10009673d 100644 --- a/src_rebuild/Game/C/dr2roads.c +++ b/src_rebuild/Game/C/dr2roads.c @@ -407,7 +407,7 @@ sdPlane* sdGetCell(VECTOR *pos) plane = &planeData[*surface]; - if (((int)plane & 3) == 0 && *(int *)plane != -1) + if (((intptr_t)plane & 3) == 0 && *(int *)plane != -1) { if (plane->surface - 16U < 16) plane = EventSurface(pos, plane); diff --git a/src_rebuild/Game/C/draw.c b/src_rebuild/Game/C/draw.c index 1c1ae2229..8d6077804 100644 --- a/src_rebuild/Game/C/draw.c +++ b/src_rebuild/Game/C/draw.c @@ -555,11 +555,11 @@ void DrawAllTheCars(int view) for (i = 0; i < num_cars_to_draw; i++) { // Don't exceed draw buffers - if ((int)(current->primtab + (-3000 - (int)(current->primptr - PRIMTAB_SIZE))) < 5800) + if ((int)(intptr_t)(current->primtab + (-3000 - (int)(intptr_t)(current->primptr - PRIMTAB_SIZE))) < 5800) return; // make cars look uglier - if ((int)(current->primtab + (-3000 - (int)(current->primptr - PRIMTAB_SIZE)) - spacefree) < 5800) + if ((int)(intptr_t)(current->primtab + (-3000 - (int)(intptr_t)(current->primptr - PRIMTAB_SIZE)) - spacefree) < 5800) gForceLowDetailCars = 1; if (cars_to_draw[i]->controlType == CONTROL_TYPE_PLAYER) diff --git a/src_rebuild/Game/C/event.c b/src_rebuild/Game/C/event.c index a063a284b..3b18ba643 100644 --- a/src_rebuild/Game/C/event.c +++ b/src_rebuild/Game/C/event.c @@ -2989,7 +2989,7 @@ void DrawEvents(int camera) pos.vx = pos.vx - boatOffset.vx; pos.vz = pos.vz - boatOffset.vz; - pos.vy = (pos.vy - boatOffset.vy) + FIXEDH((int)ev->node * RSIN(*ev->data)); + pos.vy = (pos.vy - boatOffset.vy) + FIXEDH((int)(intptr_t)ev->node * RSIN(*ev->data)); } else if (type == 0x400) { diff --git a/src_rebuild/Game/C/glaunch.c b/src_rebuild/Game/C/glaunch.c index f4dd417af..35409a6cf 100644 --- a/src_rebuild/Game/C/glaunch.c +++ b/src_rebuild/Game/C/glaunch.c @@ -288,14 +288,14 @@ void State_GameStart(void* param) void State_InitFrontEnd(void* param) { - if ((int)param == 2) + if ((int)(intptr_t)param == 2) { InitFrontend(); InitFrontendDisplay(); } else { - ReInitFrontend((int)param == 0); + ReInitFrontend((int)(intptr_t)param == 0); } SetState(STATE_FRONTEND); @@ -304,7 +304,7 @@ void State_InitFrontEnd(void* param) // [D] [T] void State_FMVPlay(void* param) { - PlayFMV((int)param); + PlayFMV((int)(intptr_t)param); SetState(STATE_INITFRONTEND, (void*)1); } @@ -376,7 +376,7 @@ void State_MissionLadder(void* param) MISSION_STEP* CurrentStep; RENDER_ARGS RenderArgs; - newgame = (int)param; + newgame = (int)(intptr_t)param; quit = 0; RenderArgs.nRenders = 0; diff --git a/src_rebuild/Game/C/mission.c b/src_rebuild/Game/C/mission.c index 4f419bd6d..0a2390660 100644 --- a/src_rebuild/Game/C/mission.c +++ b/src_rebuild/Game/C/mission.c @@ -441,7 +441,7 @@ void LoadMission(int missionnum) MissionStrings = (char*)(MissionScript + MissionLoadAddress->strings); if (MissionLoadAddress->route && !NewLevel) - loadsize = (u_int)((char*)MissionStrings + ((char*)MissionLoadAddress->route - (char*)MissionLoadAddress)); + loadsize = (u_int)(uintptr_t)((char*)MissionStrings + ((char*)MissionLoadAddress->route - (char*)MissionLoadAddress)); else loadsize = length; diff --git a/src_rebuild/Game/C/targets.c b/src_rebuild/Game/C/targets.c index c8f0722e6..d22828ae1 100644 --- a/src_rebuild/Game/C/targets.c +++ b/src_rebuild/Game/C/targets.c @@ -324,7 +324,10 @@ void DrawStopZone(VECTOR *pPosition) temp.vy = pVector->vy; temp.vz = pVector->vz; - RotTransPers(&temp, pOut, &p, &flag); + { + long otz; + gte_RotTransPers(&temp, pOut, &p, &flag, &otz); + } if (pOut == (long*)&pPoly->x0) pOut = (long*)&pPoly->x1; diff --git a/src_rebuild/Game/dr2locale.c b/src_rebuild/Game/dr2locale.c index 61f8d98ae..e3915bd29 100644 --- a/src_rebuild/Game/dr2locale.c +++ b/src_rebuild/Game/dr2locale.c @@ -5,8 +5,11 @@ #ifndef PSX #include +#include +#if !defined(__APPLE__) #include #endif +#endif int gUserLanguage = 0; diff --git a/src_rebuild/Game/dr2locale.h b/src_rebuild/Game/dr2locale.h index 1e1449a7b..5ac472c9b 100644 --- a/src_rebuild/Game/dr2locale.h +++ b/src_rebuild/Game/dr2locale.h @@ -267,8 +267,8 @@ extern char* gMissionLangTable[MAX_LANGUAGE_TEXT]; #define M_LTXT_ID(id) (char*)(id) // fancy logic -#define GET_GAME_TXT(st) (((u_intptr)st < MAX_LANGUAGE_TEXT && st) ? gGameLangTable[(int)st] : st) -#define GET_MISSION_TXT(st) (((u_intptr)st < MAX_LANGUAGE_TEXT && st) ? gMissionLangTable[(int)st] : st) +#define GET_GAME_TXT(st) (((u_intptr)st < MAX_LANGUAGE_TEXT && st) ? gGameLangTable[(int)(u_intptr)st] : st) +#define GET_MISSION_TXT(st) (((u_intptr)st < MAX_LANGUAGE_TEXT && st) ? gMissionLangTable[(int)(u_intptr)st] : st) extern int InitStringMng(); extern void DeinitStringMng(); diff --git a/src_rebuild/Game/driver2.h b/src_rebuild/Game/driver2.h index e9fc5ce6f..27b02b276 100644 --- a/src_rebuild/Game/driver2.h +++ b/src_rebuild/Game/driver2.h @@ -58,8 +58,8 @@ #define trap(ode) {printError("EXCEPTION code: %x\n", ode);} #elif _MSC_VER >= 1400 #define trap(ode) {printError("EXCEPTION code: %x\n", ode); __debugbreak();} -#elif defined(__GNUC__) -#define trap(ode) {__asm__("int3");} +#elif defined(__clang__) || defined(__GNUC__) +#define trap(ode) {__builtin_trap();} #else #define trap(ode) {_asm int 0x03} #endif diff --git a/src_rebuild/Game/psyx_compat.h b/src_rebuild/Game/psyx_compat.h index d444706c5..52434eb02 100644 --- a/src_rebuild/Game/psyx_compat.h +++ b/src_rebuild/Game/psyx_compat.h @@ -27,7 +27,7 @@ typedef u_int u_intptr; #else -#if defined(_M_X64) || defined(__amd64__) +#if defined(_M_X64) || defined(__amd64__) || defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) typedef OT_TAG OTTYPE; #else typedef unsigned long long OTTYPE; diff --git a/src_rebuild/PsyCross b/src_rebuild/PsyCross index c3e81487e..cbb9b8ac7 160000 --- a/src_rebuild/PsyCross +++ b/src_rebuild/PsyCross @@ -1 +1 @@ -Subproject commit c3e81487e9da4f43dc5ac9858b3da3126d2ff639 +Subproject commit cbb9b8ac7704306157267ffe03a6cac14b9f2332 diff --git a/src_rebuild/premake5.lua b/src_rebuild/premake5.lua index bcb805ad9..a48195b06 100644 --- a/src_rebuild/premake5.lua +++ b/src_rebuild/premake5.lua @@ -1,6 +1,7 @@ -- premake5.lua -require "premake_modules/usage" +-- "usage"/"uses" custom API was incompatible with premake5 5.0.0-beta8+; +-- we now use plain dependson+links+includedirs in each project. require "premake_modules/emscripten" IS_ANDROID = (_ACTION == "androidndk") @@ -12,6 +13,19 @@ newoption { description = "adds specific define for compiling on Raspberry Pi" } +newoption { + trigger = "renderer", + value = "API", + description = "Rendering backend (default: opengl)", + allowed = { { "opengl", "OpenGL / OpenGL ES (default)" }, + { "vulkan", "Vulkan / MoltenVK" } } +} + +if _OPTIONS["renderer"] == nil then + _OPTIONS["renderer"] = "opengl" +end +USE_VULKAN_RENDERER = (_OPTIONS["renderer"] == "vulkan") + table.insert(premake.option.get("os").allowed, { "emscripten", "Emscripten" }) ------------------------------------------ @@ -21,6 +35,26 @@ SDL2_DIR = os.getenv("SDL2_DIR") or "dependencies/SDL2" OPENAL_DIR = os.getenv("OPENAL_DIR") or "dependencies/openal-soft" JPEG_DIR = os.getenv("JPEG_DIR") or "dependencies/jpeg" +-- macOS Homebrew prefixes (Apple Silicon: /opt/homebrew, Intel: /usr/local) +HOMEBREW_PREFIX = os.getenv("HOMEBREW_PREFIX") +if HOMEBREW_PREFIX == nil and os.target() == "macosx" then + if os.isdir("/opt/homebrew") then + HOMEBREW_PREFIX = "/opt/homebrew" + else + HOMEBREW_PREFIX = "/usr/local" + end +end +if os.target() == "macosx" then + MAC_SDL2_DIR = os.getenv("MAC_SDL2_DIR") or (HOMEBREW_PREFIX .. "/opt/sdl2") + MAC_OPENAL_DIR = os.getenv("MAC_OPENAL_DIR") or (HOMEBREW_PREFIX .. "/opt/openal-soft") + MAC_JPEG_DIR = os.getenv("MAC_JPEG_DIR") or (HOMEBREW_PREFIX .. "/opt/jpeg") + if USE_VULKAN_RENDERER then + MAC_VULKAN_HEADERS = os.getenv("MAC_VULKAN_HEADERS") or (HOMEBREW_PREFIX .. "/opt/vulkan-headers") + MAC_VULKAN_LOADER = os.getenv("MAC_VULKAN_LOADER") or (HOMEBREW_PREFIX .. "/opt/vulkan-loader") + MAC_MOLTENVK = os.getenv("MAC_MOLTENVK") or (HOMEBREW_PREFIX .. "/opt/molten-vk") + end +end + WEBDEMO_DIR = os.getenv("WEBDEMO_DIR") or "../../../../content/web_demo@/" -- FIXME: make it better RED2_DIR = os.getenv("RED2_DIR") or "../../data@/" WEBSHELL_PATH = "../platform/Emscripten" -- must be relative to makefile path (SADLY) @@ -128,14 +162,23 @@ workspace "REDRIVER2" filter "platforms:*-arm64" architecture "arm64" + elseif os.target() == "macosx" then + platforms { "x64", "arm64" } + + filter "platforms:x64" + architecture "x86_64" + + filter "platforms:arm64" + architecture "ARM64" else platforms { "x86", "x64" } end startproject "REDRIVER2" - configuration "raspberry-pi" + filter "options:raspberry-pi" defines { "__RPI__" } + filter {} filter "system:Linux" buildoptions { @@ -146,18 +189,34 @@ workspace "REDRIVER2" "-Wno-unused-result", "-fpermissive" } - + cppdialect "C++11" - + filter {"system:Linux", "platforms:x86"} buildoptions { "-m32" } - + linkoptions { "-m32" } + filter "system:macosx" + buildoptions { + "-Wno-narrowing", + "-Wno-endif-labels", + "-Wno-write-strings", + "-Wno-format-security", + "-Wno-unused-result", + "-Wno-deprecated-declarations", -- OpenGL is deprecated on macOS but still works (4.1) + "-fpermissive", + -- Force-include types.h so its u_long override (uint32_t) wins over + -- macOS which would otherwise typedef u_long as 64-bit. + "-include " .. path.getabsolute("PsyCross/include/psx/types.h"), + } + + cppdialect "C++11" + filter "system:Windows" disablewarnings { "4996", "4554", "4244", "4101", "4838", "4309" } @@ -199,13 +258,14 @@ project "REDRIVER2" language "c++" targetdir "bin/%{cfg.buildcfg}" - includedirs { - "Game", + includedirs { + "Game", + "PsyCross/include", + "PsyCross/include/psx", } - - uses { - "PsyCross", - } + + dependson { "PsyCross" } + links { "PsyCross" } defines { GAME_REGION } defines { "BUILD_CONFIGURATION_STRING=\"%{cfg.buildcfg}\"" } @@ -221,10 +281,10 @@ project "REDRIVER2" "Game/**.c" } - filter {"system:Windows or linux or platforms:emscripten"} + filter {"system:Windows or linux or macosx or platforms:emscripten"} --dependson { "PsyX" } links { "jpeg" } - + files { "utils/**.h", "utils/**.cpp", @@ -278,6 +338,57 @@ project "REDRIVER2" "dl", } + filter "system:macosx" + includedirs { + MAC_SDL2_DIR.."/include", + MAC_SDL2_DIR.."/include/SDL2", + MAC_OPENAL_DIR.."/include", + MAC_JPEG_DIR.."/include", + } + + libdirs { + MAC_SDL2_DIR.."/lib", + MAC_OPENAL_DIR.."/lib", + MAC_JPEG_DIR.."/lib", + } + + links { + "Cocoa.framework", + "openal", + "SDL2", + "jpeg", + "dl", + } + + -- Embed Homebrew rpaths so the binary finds dylibs at runtime + linkoptions { + "-Wl,-rpath," .. MAC_SDL2_DIR .. "/lib", + "-Wl,-rpath," .. MAC_OPENAL_DIR .. "/lib", + "-Wl,-rpath," .. MAC_JPEG_DIR .. "/lib", + } + + if USE_VULKAN_RENDERER then + local SHADERC_DIR = HOMEBREW_PREFIX .. "/opt/shaderc" + defines { "RENDERER_VK", "USE_VULKAN", "USE_OPENGL_RENDERER=0" } + includedirs { + MAC_VULKAN_HEADERS .. "/include", + SHADERC_DIR .. "/include", + } + libdirs { + MAC_VULKAN_LOADER .. "/lib", + SHADERC_DIR .. "/lib", + } + links { "vulkan", "Metal.framework", "QuartzCore.framework", "IOSurface.framework" } + -- libshaderc_combined.a is referenced from libPsyCross.a; the + -- final executable must pull it in directly to satisfy symbols. + linkoptions { + "-Wl,-rpath," .. MAC_VULKAN_LOADER .. "/lib", + SHADERC_DIR .. "/lib/libshaderc_combined.a", + } + else + links { "OpenGL.framework" } + end + filter "configurations:Debug" targetsuffix "_dbg" defines { diff --git a/src_rebuild/premake5_psycross.lua b/src_rebuild/premake5_psycross.lua index 015a73479..75fcbce3d 100644 --- a/src_rebuild/premake5_psycross.lua +++ b/src_rebuild/premake5_psycross.lua @@ -56,6 +56,57 @@ project "PsyCross" "SDL2", } + filter "system:macosx" + includedirs { + MAC_SDL2_DIR.."/include", + MAC_SDL2_DIR.."/include/SDL2", + MAC_OPENAL_DIR.."/include", + } + + libdirs { + MAC_SDL2_DIR.."/lib", + MAC_OPENAL_DIR.."/lib", + } + + links { + "Cocoa.framework", + "openal", + "SDL2", + } + + if USE_VULKAN_RENDERER then + local SHADERC_DIR = HOMEBREW_PREFIX .. "/opt/shaderc" + defines { "RENDERER_VK", "USE_VULKAN", "USE_OPENGL_RENDERER=0" } + includedirs { + MAC_VULKAN_HEADERS .. "/include", + SHADERC_DIR .. "/include", + } + libdirs { + MAC_VULKAN_LOADER .. "/lib", + SHADERC_DIR .. "/lib", + } + -- libshaderc_combined.a bundles glslang+SPIRV-Tools statically; + -- gives us GLSL→SPIR-V compilation at runtime without any dylib. + linkoptions { SHADERC_DIR .. "/lib/libshaderc_combined.a" } + links { + "vulkan", + "Metal.framework", + "QuartzCore.framework", + "IOSurface.framework", + } + -- Exclude OpenGL backend sources when building Vulkan + removefiles { + "PsyCross/src/render/PsyX_render.cpp", + "PsyCross/src/render/glad.c", + } + else + links { "OpenGL.framework" } + -- Exclude Vulkan backend sources when building OpenGL + removefiles { + "PsyCross/src/render/PsyX_render_vk.cpp", + } + end + filter "configurations:Release" optimize "Speed" diff --git a/src_rebuild/premake_modules/usage/usage.lua b/src_rebuild/premake_modules/usage/usage.lua index d3ecc3271..a47a3fafe 100644 --- a/src_rebuild/premake_modules/usage/usage.lua +++ b/src_rebuild/premake_modules/usage/usage.lua @@ -72,14 +72,17 @@ local sourcesStack = Stack:Create() -- --- 'uses' api +-- 'uses' api (only register if not already provided by core premake) -- -p.api.register { - name = "uses", - scope = { "config" }, - kind = "list:string", - } +if not (p.field and p.field.get and p.field.get("uses")) then + local ok = pcall(p.api.register, { + name = "uses", + scope = { "config" }, + kind = "list:string", + }) + -- if already registered by core premake, that's fine +end -- -- 'usage' container diff --git a/src_rebuild/tools/font_tool/font_tool_main.cpp b/src_rebuild/tools/font_tool/font_tool_main.cpp index 407d29161..f421936f8 100644 --- a/src_rebuild/tools/font_tool/font_tool_main.cpp +++ b/src_rebuild/tools/font_tool/font_tool_main.cpp @@ -1,6 +1,8 @@ #include #include +#if !defined(__APPLE__) #include +#endif #include #include diff --git a/src_rebuild/utils/audio_source/snd_wav_cache.cpp b/src_rebuild/utils/audio_source/snd_wav_cache.cpp index 0f36aaaf1..dfe07acba 100644 --- a/src_rebuild/utils/audio_source/snd_wav_cache.cpp +++ b/src_rebuild/utils/audio_source/snd_wav_cache.cpp @@ -1,5 +1,8 @@ #include "snd_wav_cache.h" +#if !defined(__APPLE__) #include +#endif +#include #include bool CSoundSource_WaveCache::Load(const char* szFilename) diff --git a/src_rebuild/utils/fs.cpp b/src_rebuild/utils/fs.cpp index a02048b1c..4f8146ee7 100644 --- a/src_rebuild/utils/fs.cpp +++ b/src_rebuild/utils/fs.cpp @@ -8,8 +8,6 @@ #include #include -#define HOME_ENV "USERPROFILE" - void FS_FixPathSlashes(char* pathbuff) { while (*pathbuff) @@ -20,11 +18,14 @@ void FS_FixPathSlashes(char* pathbuff) } } -#elif defined (__unix__) +#elif defined (__unix__) || defined(__APPLE__) #include #include // glob(), globfree() +#include // malloc(), free() +#if !defined(__APPLE__) #include +#endif void FS_FixPathSlashes(char* pathbuff) { diff --git a/src_rebuild/utils/fs.h b/src_rebuild/utils/fs.h index c06fdacb6..0f6e7153c 100644 --- a/src_rebuild/utils/fs.h +++ b/src_rebuild/utils/fs.h @@ -5,7 +5,7 @@ #include #define HOME_ENV "USERPROFILE" -#elif defined (__unix__) +#elif defined (__unix__) || defined(__APPLE__) #include #define HOME_ENV "HOME" diff --git a/src_rebuild/utils/targa.cpp b/src_rebuild/utils/targa.cpp index ffecd628c..e085a0265 100644 --- a/src_rebuild/utils/targa.cpp +++ b/src_rebuild/utils/targa.cpp @@ -1,7 +1,10 @@ #include "targa.h" #include #include +#include +#if !defined(__APPLE__) #include +#endif bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, int& bpp) { diff --git a/src_rebuild/utils/video_source/VideoPlayer.cpp b/src_rebuild/utils/video_source/VideoPlayer.cpp index 666b105e4..1f2503916 100644 --- a/src_rebuild/utils/video_source/VideoPlayer.cpp +++ b/src_rebuild/utils/video_source/VideoPlayer.cpp @@ -448,10 +448,12 @@ void DrawFrame(ReadAVI::stream_format_t& stream_format, int frame_number, int cr PsyX_BeginScene(); GR_Clear(0, 0, windowWidth, windowHeight, 0, 0, 0); - + +#if defined(RENDERER_OGL) || defined(RENDERER_OGLES) glBindTexture(GL_TEXTURE_2D, g_FMVTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, image_w, image_h, 0, GL_RGB, GL_UNSIGNED_BYTE, g_FMVDecodedImageBuffer); glBindTexture(GL_TEXTURE_2D, 0); +#endif GR_SetShader(g_FMVShader); GR_SetTexture(g_FMVTexture, (TexFormat)-1); From d9b184986fe8fe4ceec52d3084c5f4c4ae468a4e Mon Sep 17 00:00:00 2001 From: Erasmo Bellumat Date: Thu, 30 Apr 2026 20:02:13 -0300 Subject: [PATCH 2/4] Game-side flicker fixes: PVS decoder UB, frustum culling overflow, offscreen leak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three structural bugs that were producing flicker / popping under both the OpenGL and Vulkan backends on Apple Silicon (and likely under any LP64 + clang target). PVS decoder undefined behavior (Game/C/map.c) - PVSDecode had two `i++` reads in the same expression with no sequence point between them: sym = ((sym & 3) * 16 + nybblearray[i++]) * 16 + nybblearray[i++ + 1]; Order of evaluation between the two side-effects is unspecified in C; clang on arm64 reordered the reads vs MSVC / gcc-x64 and the second post-increment consumed the wrong index. Result: visibility bits for the inner cells were reading garbage nibbles → cells in the centre of the PVS region toggled visible / invisible each frame the table was rebuilt → buildings and ground popped in dense areas. Fix: read the two nibbles into locals before combining. - The same function's final memcpy was copying `pvs_square_sq - 1` bytes, leaving the last cell of every PVS table as uninitialized stack memory. Fix: copy the full table. Frustum culling overflow (Game/ASM/rndrasm.c, Game/C/draw.c, tile.c, sky.c) - Apply_Inv_CameraMatrix and friends did the matrix-vector product as `int * int + int * int + int * int` then >> ONE_BITS. On 64-bit targets the operands are still 32-bit ints, so the additive sum can overflow before the shift even though the final shifted result fits. Routed through new FixedDot helper that promotes each MULT to long long before summing. - FrustrumCheck16 now takes the cell's `nearCell` reference and unpacks the cell object via QuickUnpackCellObject before computing the matrix-vector product. The previous code read pcop->pos directly, which is the *packed* delta (relative to the cell's region origin) — fine on PSX where the GTE quietly handles the scale, but on the non-PSX path it produced positions an entire region out of place. Cells flicked in/out of view as the camera rotated through region boundaries. - DrawSprites / DrawTILES updated to take `CELL_OBJECT**` (unpacked) instead of `PACKED_CELL_OBJECT**`. DrawMapPSX unpacks into `sprite_objects[]` / `model_tile_objects[]` static arrays before passing to the draw routines. Same root cause as above — pco->pos.vx/vy/vz were being treated as world coords, but they're packed deltas. - PVS_ptr: the hardcoded `+220` was the PSX-only constant for a 20x20 PVS at the centre cell. Replaced by `view_dist * pvs_square + view_dist`, which is correct for any pvs_square the engine configures. - newPositionVisible (map.c): same dimension fix — `cellx + view_dist + (cellz + view_dist) * pvs_square`. - Sky vertex PGXP indices forced to 0xffff so sky polys take the 2D path (they were never running the 3D PGXP transform anyway, the pgxp_index = PGXP_GetIndex(0)-1 hack fed stale cache entries). Offscreen DRAWENV leak (PsyCross side, see submodule bump) - The Vulkan backend now early-returns from GR_DrawTriangles when activeDrawEnv.dfe = 0. PSX's `dfe` flag means "draw in display area" — when 0, the engine intends to render to VRAM aux memory, not to the swapchain. The previous backend ignored the flag and every offscreen pass leaked onto the screen, layering on top of the actual frame in an order that varied with split count. Camera (Game/C/camera.c) - Null guard on `cp->ap.carCos` in PlaceCameraInCar — the previous `if (cp)` was insufficient because the cosmetics pointer can be unset for a cleanly initialized car_data slot, and the next line immediately dereferenced `cp->ap.carCos->colBox.vz`. Diagnostic plumbing - DrawMapPSX and the new SetupDrawMapPSX print [DRAWDIAG] stats when REDRIVER2_DRAW_DIAG=1: cells tested / visible / rejected by plane, range, PVS or OOB index; objects packed / frustum-passed / frustum-rejected; per-list overflow counts. Interval via REDRIVER2_DRAW_DIAG_INTERVAL. Submodule - src_rebuild/PsyCross bumped to the matching commit on github.com/ebellumat/PsyCross @ vulkan-port. --- src_rebuild/Game/ASM/rndrasm.c | 80 ++++++--- src_rebuild/Game/ASM/rndrasm.h | 2 +- src_rebuild/Game/C/camera.c | 2 +- src_rebuild/Game/C/draw.c | 320 +++++++++++++++++++++++++++++---- src_rebuild/Game/C/draw.h | 6 + src_rebuild/Game/C/map.c | 21 ++- src_rebuild/Game/C/sky.c | 20 +-- src_rebuild/Game/C/tile.c | 23 +-- src_rebuild/Game/C/tile.h | 2 +- src_rebuild/PsyCross | 2 +- 10 files changed, 371 insertions(+), 107 deletions(-) diff --git a/src_rebuild/Game/ASM/rndrasm.c b/src_rebuild/Game/ASM/rndrasm.c index 3ed7af1ec..3db61a618 100644 --- a/src_rebuild/Game/ASM/rndrasm.c +++ b/src_rebuild/Game/ASM/rndrasm.c @@ -1,9 +1,34 @@ #include "driver2.h" #include "C/camera.h" #include "C/draw.h" +#include "C/cell.h" #define FRUSTUM_THRESHOLD (-80) +#ifndef PSX +static int FixedDot(const MATRIX& matrix, int row, int vx, int vy, int vz) +{ + return (int)(((long long)matrix.m[row][0] * vx + + (long long)matrix.m[row][1] * vy + + (long long)matrix.m[row][2] * vz) >> ONE_BITS); +} + +static void ApplyMatrixFull(const MATRIX& matrix, VECTOR* out, int vx, int vy, int vz) +{ + out->vx = FixedDot(matrix, 0, vx, vy, vz); + out->vy = FixedDot(matrix, 1, vx, vy, vz); + out->vz = FixedDot(matrix, 2, vx, vy, vz); + out->pad = 0; +} + +static void ApplyMatrixFullNoPad(const MATRIX& matrix, VECTOR_NOPAD* out, int vx, int vy, int vz) +{ + out->vx = FixedDot(matrix, 0, vx, vy, vz); + out->vy = FixedDot(matrix, 1, vx, vy, vz); + out->vz = FixedDot(matrix, 2, vx, vy, vz); +} +#endif + // [D] [T] void SetCameraVector(void) { @@ -19,13 +44,7 @@ void Apply_Inv_CameraMatrix(VECTOR* v) gte_stlvl(v); #else VECTOR local = *v; - MATRIX& lc = inv_camera_matrix; - //gte_ReadColorMatrix(&lc); // in general this is a inv_camera_matrix - - // lcir is limited to short vector values so we do this instead - v->vx = FIXED(lc.m[0][0] * local.vx + lc.m[0][1] * local.vy + lc.m[0][2] * local.vz); - v->vy = FIXED(lc.m[1][0] * local.vx + lc.m[1][1] * local.vy + lc.m[1][2] * local.vz); - v->vz = FIXED(lc.m[2][0] * local.vx + lc.m[2][1] * local.vy + lc.m[2][2] * local.vz); + ApplyMatrixFull(inv_camera_matrix, v, local.vx, local.vy, local.vz); #endif } @@ -33,23 +52,17 @@ void Apply_Inv_CameraMatrix(VECTOR* v) int Apply_InvCameraMatrixSetTrans(VECTOR_NOPAD* pos) { VECTOR vfc, vec; - SVECTOR local; gte_stfc(&vfc); +#ifdef PSX + SVECTOR local; VecSubtract(&local, pos, &vfc); -#ifdef PSX gte_ldsv(&local); gte_lcir(); gte_stlvl(&vec); #else - MATRIX& lc = inv_camera_matrix; - //gte_ReadColorMatrix(&lc); // in general this is a inv_camera_matrix - - // lcir is limited to short vector values so we do this instead - vec.vx = FIXED(lc.m[0][0] * local.vx + lc.m[0][1] * local.vy + lc.m[0][2] * local.vz); - vec.vy = FIXED(lc.m[1][0] * local.vx + lc.m[1][1] * local.vy + lc.m[1][2] * local.vz); - vec.vz = FIXED(lc.m[2][0] * local.vx + lc.m[2][1] * local.vy + lc.m[2][2] * local.vz); + ApplyMatrixFull(inv_camera_matrix, &vec, pos->vx - vfc.vx, pos->vy - vfc.vy, pos->vz - vfc.vz); #endif gte_SetTransVector(&vec); @@ -63,24 +76,17 @@ int Apply_InvCameraMatrixSetTrans(VECTOR_NOPAD* pos) int Apply_InvCameraMatrixAndSetMatrix(VECTOR_NOPAD* pos, MATRIX2* mtx) { VECTOR vfc, vec; - SVECTOR local; gte_stfc(&vfc); +#ifdef PSX + SVECTOR local; VecSubtract(&local, pos, &vfc); -#ifdef PSX gte_ldsv(&local); gte_lcir(); gte_stlvl(&vec); #else - - MATRIX& lc = inv_camera_matrix; - //gte_ReadColorMatrix(&lc); // in general this is a inv_camera_matrix - - // lcir is limited to short vector values so we do this instead - vec.vx = FIXED(lc.m[0][0] * local.vx + lc.m[0][1] * local.vy + lc.m[0][2] * local.vz); - vec.vy = FIXED(lc.m[1][0] * local.vx + lc.m[1][1] * local.vy + lc.m[1][2] * local.vz); - vec.vz = FIXED(lc.m[2][0] * local.vx + lc.m[2][1] * local.vy + lc.m[2][2] * local.vz); + ApplyMatrixFull(inv_camera_matrix, &vec, pos->vx - vfc.vx, pos->vy - vfc.vy, pos->vz - vfc.vz); #endif gte_SetRotMatrix(mtx); @@ -93,17 +99,27 @@ int Apply_InvCameraMatrixAndSetMatrix(VECTOR_NOPAD* pos, MATRIX2* mtx) } // [D] [T] -int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, int bounding_sphere) +int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, XZPAIR* near, int bounding_sphere) { VECTOR_NOPAD result; SVECTOR local; int ang; + +#ifdef PSX VecSubtract(&local, &pcop->pos, &camera_position); gte_ldsv(&local); gte_llir(); gte_stlvnl(&result); +#else + CELL_OBJECT cellObject; + QuickUnpackCellObject(pcop, near, &cellObject); + ApplyMatrixFullNoPad(frustrum_matrix, &result, + cellObject.pos.vx - camera_position.vx, + cellObject.pos.vy - camera_position.vy, + cellObject.pos.vz - camera_position.vz); +#endif ang = FRUSTUM_THRESHOLD - bounding_sphere; @@ -119,14 +135,22 @@ int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, int bounding_sphere) int FrustrumCheck(VECTOR* pos, int bounding_sphere) { VECTOR_NOPAD result; - SVECTOR local; int ang; + +#ifdef PSX + SVECTOR local; VecSubtract(&local, pos, &camera_position); gte_ldsv(&local); gte_llir(); gte_stlvnl(&result); +#else + ApplyMatrixFullNoPad(frustrum_matrix, &result, + pos->vx - camera_position.vx, + pos->vy - camera_position.vy, + pos->vz - camera_position.vz); +#endif ang = FRUSTUM_THRESHOLD - bounding_sphere; diff --git a/src_rebuild/Game/ASM/rndrasm.h b/src_rebuild/Game/ASM/rndrasm.h index 4b358453a..94f24c862 100644 --- a/src_rebuild/Game/ASM/rndrasm.h +++ b/src_rebuild/Game/ASM/rndrasm.h @@ -8,7 +8,7 @@ extern void Apply_Inv_CameraMatrix(VECTOR* v); // 0x0001BCFC extern int Apply_InvCameraMatrixSetTrans(VECTOR_NOPAD* pos); extern int Apply_InvCameraMatrixAndSetMatrix(VECTOR_NOPAD* pos, MATRIX2* mtx); -extern int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, int bounding_sphere); // 0x0001BD30 +extern int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, XZPAIR* near, int bounding_sphere); // 0x0001BD30 extern int FrustrumCheck(VECTOR* pos, int bounding_sphere); // 0x0001BDEC diff --git a/src_rebuild/Game/C/camera.c b/src_rebuild/Game/C/camera.c index 9dfe2548d..bffdac222 100644 --- a/src_rebuild/Game/C/camera.c +++ b/src_rebuild/Game/C/camera.c @@ -593,7 +593,7 @@ void PlaceCameraInCar(PLAYER *lp, int BumperCam) else camera_angle.vy = (lp->headPos >> 16) - baseDir & 4095; - if (cp) + if (cp && cp->ap.carCos) { // build custom matrix using car draw matrix InvertMatrix(&cp->hd.drawCarMat, &inv_camera_matrix); diff --git a/src_rebuild/Game/C/draw.c b/src_rebuild/Game/C/draw.c index 8d6077804..726de4f8c 100644 --- a/src_rebuild/Game/C/draw.c +++ b/src_rebuild/Game/C/draw.c @@ -1,5 +1,7 @@ #include "driver2.h" +#include + #include "draw.h" #include "main.h" #include "map.h" @@ -8,6 +10,7 @@ #include "camera.h" #include "mission.h" #include "cell.h" +#include "spool.h" #include "tile.h" #include "objanim.h" #include "texture.h" @@ -89,6 +92,8 @@ void* model_object_ptrs[MAX_DRAWN_BUILDINGS]; void* model_tile_ptrs[MAX_DRAWN_TILES]; void* anim_obj_buffer[MAX_DRAWN_ANIMATING]; void* spriteList[MAX_DRAWN_SPRITES]; +CELL_OBJECT model_tile_objects[MAX_DRAWN_TILES]; +CELL_OBJECT sprite_objects[MAX_DRAWN_SPRITES]; MATRIX inv_camera_matrix; MATRIX face_camera; @@ -105,12 +110,93 @@ int fasterToggle = 0; int combointensity; -char CurrentPVS[PVS_CELL_COUNT * PVS_CELL_COUNT + 3]; // 20*20+4 +char CurrentPVS[PVS_CELL_COUNT * PVS_CELL_COUNT + 3]; MATRIX2 matrixtable[64]; int setupYet = 0; int gDrawDistance = PVS_CELL_COUNT * PVS_CELL_COUNT; +#ifndef PSX +struct DrawMapDiagStats +{ + int cells_tested; + int cells_visible; + int cells_range_rejected; + int cells_plane_rejected; + int cells_pvs_rejected; + int cells_unloaded_region; + int pvs_oob_reads; + int packed_objects; + int frustum_passed; + int frustum_rejected; + int sprites_overflow; + int tiles_overflow; + int buildings_overflow; + int anim_overflow; +}; + +struct DrawPvsDiagState +{ + int source_region; + int region; + int cell; + int reloaded; + int reload_count; + int last_reload_frame; + int loaded_region; + int loading_region; + int visible_entries; +}; + +static DrawPvsDiagState gDrawPvsDiag; + +static int DrawDiagEnabled(void) +{ + static int initialized = 0; + static int enabled = 0; + + if (!initialized) + { + const char* value = getenv("REDRIVER2_DRAW_DIAG"); + + if (value == NULL) + value = getenv("REDRIVER2_VK_DIAG"); + + enabled = value != NULL && value[0] != '\0' && value[0] != '0'; + initialized = 1; + } + + return enabled; +} + +static int DrawDiagInterval(void) +{ + static int initialized = 0; + static int interval = 60; + + if (!initialized) + { + const char* value = getenv("REDRIVER2_DRAW_DIAG_INTERVAL"); + + if (value == NULL) + value = getenv("REDRIVER2_VK_DIAG_INTERVAL"); + + if (value != NULL) + { + int parsed = atoi(value); + + if (parsed > 0) + interval = parsed; + } + + initialized = 1; + } + + return interval; +} + +#endif + #ifndef PSX _pct& plotContext = *(_pct*)((u_char*)getScratchAddr(0) + 1024 - sizeof(_pct)); // orig offset: 0x1f800020 #endif @@ -162,15 +248,15 @@ void addSubdivSpriteShadow(POLYFT4* src, SVECTOR* verts, int z) } // [D] [T] [A] -void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) +void DrawSprites(CELL_OBJECT** sprites, int numFound) { int i; int z; u_int spriteColour, lightdd; u_char lightLevel; MODEL* model; - PACKED_CELL_OBJECT* pco; - PACKED_CELL_OBJECT** list; + CELL_OBJECT* pco; + CELL_OBJECT** list; int numShadows; #if 0 //def PSX @@ -228,10 +314,10 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) pco = *list; list++; - modelnumber = (pco->value >> 6) | (pco->pos.vy & 1) << 10; + modelnumber = pco->type; model = modelpointers[modelnumber]; - if ((pco->value & 63) == 63 || litSprites[modelnumber >> 5] & 1 << (modelnumber & 31)) // [A] multiple sprites lighting fixes + if (pco->yang == 63 || litSprites[modelnumber >> 5] & 1 << (modelnumber & 31)) // [A] multiple sprites lighting fixes { plotContext.colour = 0x2c808080; } @@ -240,11 +326,7 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) plotContext.colour = spriteColour; } - plotContext.scribble[0] = pco->pos.vx; - plotContext.scribble[1] = (pco->pos.vy << 0x10) >> 0x11; - plotContext.scribble[2] = pco->pos.vz; - - z = Apply_InvCameraMatrixAndSetMatrix((VECTOR_NOPAD*)plotContext.scribble, (MATRIX2*)&face_camera); + z = Apply_InvCameraMatrixAndSetMatrix(&pco->pos, (MATRIX2*)&face_camera); if (z < 1000) { @@ -299,7 +381,7 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) #endif if (wetness == 0 && gTimeOfDay != TIME_NIGHT && - (pco->value & 32) == 0 && + (pco->yang & 32) == 0 && z < MAX_TREE_SHADOW_DISTANCE && numShadows < 40) { @@ -364,7 +446,11 @@ void SetupPlaneColours(u_int ambient) } -int current_pvs_cell; +int current_pvs_cell = -1; +int current_pvs_region = -1; +int current_pvs_source_region = -1; +int current_pvs_loaded_region = -2; +int current_pvs_loading_region = -2; // [D] [T] @@ -373,6 +459,14 @@ void SetupDrawMapPSX(void) int cell_x, cell_z; int theta; int pvs_cell; + int region_x1; + int region_z1; + int current_barrel_region_x1; + int current_barrel_region_z1; + int pvs_region; + int pvs_source_region; + int pvs_loaded_region; + int pvs_loading_region; if (setupYet != 0) { @@ -385,23 +479,55 @@ void SetupDrawMapPSX(void) current_cell_x = cell_x; current_cell_z = cell_z; - pvs_cell = (cell_z % MAP_REGION_SIZE) * MAP_REGION_SIZE + (cell_x % MAP_REGION_SIZE); - if (pvs_cell != current_pvs_cell) - { - int region_x1, region_z1; - int current_barrel_region_x1, current_barrel_region_z1; + region_x1 = cell_x / MAP_REGION_SIZE; + region_z1 = cell_z / MAP_REGION_SIZE; + + current_barrel_region_x1 = (region_x1 & 1); + current_barrel_region_z1 = (region_z1 & 1); - region_x1 = cell_x / MAP_REGION_SIZE; - region_z1 = cell_z / MAP_REGION_SIZE; + pvs_region = region_x1 + region_z1 * regions_across; + pvs_source_region = current_barrel_region_x1 + current_barrel_region_z1 * 2; + pvs_cell = (cell_z - region_z1 * MAP_REGION_SIZE) * MAP_REGION_SIZE + cell_x - region_x1 * MAP_REGION_SIZE; + pvs_loaded_region = regions_unpacked[pvs_source_region]; + pvs_loading_region = loading_region[pvs_source_region]; - current_barrel_region_x1 = (region_x1 & 1); - current_barrel_region_z1 = (region_z1 & 1); +#ifndef PSX + gDrawPvsDiag.source_region = pvs_source_region; + gDrawPvsDiag.region = pvs_region; + gDrawPvsDiag.cell = pvs_cell; + gDrawPvsDiag.reloaded = (gDrawPvsDiag.reload_count > 0 && gDrawPvsDiag.last_reload_frame == FrameCnt); + gDrawPvsDiag.loaded_region = pvs_loaded_region; + gDrawPvsDiag.loading_region = pvs_loading_region; +#endif + if (pvs_cell != current_pvs_cell || + pvs_region != current_pvs_region || + pvs_source_region != current_pvs_source_region || + pvs_loaded_region != current_pvs_loaded_region || + pvs_loading_region != current_pvs_loading_region) + { current_pvs_cell = pvs_cell; + current_pvs_region = pvs_region; + current_pvs_source_region = pvs_source_region; + current_pvs_loaded_region = pvs_loaded_region; + current_pvs_loading_region = pvs_loading_region; GetPVSRegionCell2( - current_barrel_region_x1 + current_barrel_region_z1 * 2, - region_x1 + region_z1 * regions_across, + pvs_source_region, + pvs_region, pvs_cell, CurrentPVS); + +#ifndef PSX + gDrawPvsDiag.reloaded = 1; + gDrawPvsDiag.reload_count++; + gDrawPvsDiag.last_reload_frame = FrameCnt; + gDrawPvsDiag.visible_entries = 0; + + for (int i = 0; i < pvs_square_sq; i++) + { + if (CurrentPVS[i]) + gDrawPvsDiag.visible_entries++; + } +#endif } InitFrustrumMatrix(); @@ -1301,6 +1427,10 @@ void DrawMapPSX(int* comp_val) static int treecount = 0; static int alleycount = 0; +#ifndef PSX + DrawMapDiagStats diag = {}; +#endif + SetupDrawMapPSX(); // clean cell cache @@ -1344,7 +1474,7 @@ void DrawMapPSX(int* comp_val) drawData.cellzpos = current_cell_z; drawData.cellxpos = current_cell_x; - PVS_ptr = CurrentPVS + 220; + PVS_ptr = CurrentPVS + view_dist * pvs_square + view_dist; vloop = 0; hloop = 0; @@ -1364,38 +1494,99 @@ void DrawMapPSX(int* comp_val) { if (ABS(hloop) + ABS(vloop) < PVS_CELL_COUNT) { +#ifndef PSX + diag.cells_tested++; +#endif // clamped vis values - int vis_h = MIN(MAX(hloop, -9), PVS_CELL_COUNT / 2); - int vis_v = MIN(MAX(vloop, -9), PVS_CELL_COUNT / 2); + int vis_h = MIN(MAX(hloop, 1 - view_dist), view_dist); + int vis_v = MIN(MAX(vloop, 1 - view_dist), view_dist); + int pvs_index = view_dist * pvs_square + view_dist + vis_v * pvs_square + vis_h; cellx = drawData.cellxpos + hloop; cellz = drawData.cellzpos + vloop; - if (drawData.rightPlane < 0 && - drawData.leftPlane > 0 && - drawData.backPlane < drawData.farClipLimit && // check planes - cellx > -1 && cellx < cells_across && // check cell ranges - cellz > -1 && cellz < cells_down && - PVS_ptr[vis_v * pvs_square + vis_h]) // check PVS table + if (pvs_index < 0 || pvs_index >= pvs_square_sq) { +#ifndef PSX + diag.pvs_oob_reads++; +#endif + } + else if (drawData.rightPlane >= 0 || + drawData.leftPlane <= 0 || + drawData.backPlane >= drawData.farClipLimit) + { +#ifndef PSX + diag.cells_plane_rejected++; +#endif + } + else if (cellx <= -1 || cellx >= cells_across || + cellz <= -1 || cellz >= cells_down) + { +#ifndef PSX + diag.cells_range_rejected++; +#endif + } + else if (!PVS_ptr[vis_v * pvs_square + vis_h]) + { +#ifndef PSX + diag.cells_pvs_rejected++; +#endif + } + else + { +#ifndef PSX + diag.cells_visible++; + { + int region_x = cellx / MAP_REGION_SIZE; + int region_z = cellz / MAP_REGION_SIZE; + int source_region = (region_x & 1) + (region_z & 1) * 2; + int region = region_x + region_z * regions_across; + + if (loading_region[source_region] != -1 || RoadMapRegions[source_region] != region) + diag.cells_unloaded_region++; + } +#endif // walk each cell object in cell for (ppco = GetFirstPackedCop(cellx, cellz, &ci, 1, drawData.cellLevel); ppco; ppco = GetNextPackedCop(&ci)) { model = modelpointers[(ppco->value >> 6) | ((ppco->pos).vy & 1) << 10]; - if (FrustrumCheck16(ppco, model->bounding_sphere) != -1) +#ifndef PSX + diag.packed_objects++; +#endif + + if (FrustrumCheck16(ppco, &ci.nearCell, model->bounding_sphere) != -1) { +#ifndef PSX + diag.frustum_passed++; +#endif // sprity type if (model->shape_flags & SHAPE_FLAG_SPRITE) { if (drawData.sprites_found < MAX_DRAWN_SPRITES) - spriteList[drawData.sprites_found++] = ppco; + { + QuickUnpackCellObject(ppco, &ci.nearCell, &sprite_objects[drawData.sprites_found]); + spriteList[drawData.sprites_found] = &sprite_objects[drawData.sprites_found]; + drawData.sprites_found++; + } +#ifndef PSX + else + { + diag.sprites_overflow++; + } +#endif if ((model->flags2 & MODEL_FLAG_ANIMOBJ) && drawData.anim_objs_found < MAX_DRAWN_ANIMATING) { cop = UnpackCellObject(ppco, &ci.nearCell); anim_obj_buffer[drawData.anim_objs_found++] = cop; } +#ifndef PSX + else if (model->flags2 & MODEL_FLAG_ANIMOBJ) + { + diag.anim_overflow++; + } +#endif if (model->flags2 & MODEL_FLAG_TREE) { @@ -1453,7 +1644,17 @@ void DrawMapPSX(int* comp_val) } if (drawData.tiles_found < MAX_DRAWN_TILES) - model_tile_ptrs[drawData.tiles_found++] = ppco; + { + QuickUnpackCellObject(ppco, &ci.nearCell, &model_tile_objects[drawData.tiles_found]); + model_tile_ptrs[drawData.tiles_found] = &model_tile_objects[drawData.tiles_found]; + drawData.tiles_found++; + } +#ifndef PSX + else + { + diag.tiles_overflow++; + } +#endif } else { @@ -1461,12 +1662,26 @@ void DrawMapPSX(int* comp_val) if (drawData.other_models_found < MAX_DRAWN_BUILDINGS) model_object_ptrs[drawData.other_models_found++] = cop; +#ifndef PSX + else + diag.buildings_overflow++; +#endif if (drawData.anim_objs_found < MAX_DRAWN_ANIMATING && (model->flags2 & MODEL_FLAG_ANIMOBJ)) anim_obj_buffer[drawData.anim_objs_found++] = cop; +#ifndef PSX + else if (model->flags2 & MODEL_FLAG_ANIMOBJ) + diag.anim_overflow++; +#endif } } } +#ifndef PSX + else + { + diag.frustum_rejected++; + } +#endif } } } @@ -1503,6 +1718,33 @@ void DrawMapPSX(int* comp_val) } }while (i-- > 0); +#ifndef PSX + if (DrawDiagEnabled()) + { + int interval = DrawDiagInterval(); + + if (FrameCnt % interval == 0) + { + printInfo("[DRAWDIAG] frame=%d cam=%d,%d,%d cell=%d,%d pvs src/reg/cell=%d/%d/%d reload=%d last/count=%d/%d loaded/loading=%d/%d entries=%d cells vis/test=%d/%d reject plane/range/pvs/oob=%d/%d/%d/%d unloaded=%d objects=%d frustum=%d/%d draw b/s/t/a=%d/%d/%d/%d overflow b/s/t/a=%d/%d/%d/%d\n", + FrameCnt, + camera_position.vx, camera_position.vy, camera_position.vz, + drawData.cellxpos, drawData.cellzpos, + gDrawPvsDiag.source_region, gDrawPvsDiag.region, gDrawPvsDiag.cell, + gDrawPvsDiag.reloaded, + gDrawPvsDiag.last_reload_frame, gDrawPvsDiag.reload_count, + gDrawPvsDiag.loaded_region, gDrawPvsDiag.loading_region, + gDrawPvsDiag.visible_entries, + diag.cells_visible, diag.cells_tested, + diag.cells_plane_rejected, diag.cells_range_rejected, diag.cells_pvs_rejected, diag.pvs_oob_reads, + diag.cells_unloaded_region, + diag.packed_objects, + diag.frustum_passed, diag.frustum_rejected, + drawData.other_models_found, drawData.sprites_found, drawData.tiles_found, drawData.anim_objs_found, + diag.buildings_overflow, diag.sprites_overflow, diag.tiles_overflow, diag.anim_overflow); + } + } +#endif + #if 0 char tempBuf[512]; @@ -1530,10 +1772,10 @@ void DrawMapPSX(int* comp_val) DrawAllAnimatingObjects((CELL_OBJECT**)anim_obj_buffer, drawData.anim_objs_found); if (drawData.sprites_found) - DrawSprites((PACKED_CELL_OBJECT**)spriteList, drawData.sprites_found); + DrawSprites((CELL_OBJECT**)spriteList, drawData.sprites_found); if (drawData.tiles_found) - DrawTILES((PACKED_CELL_OBJECT**)model_tile_ptrs, drawData.tiles_found); + DrawTILES((CELL_OBJECT**)model_tile_ptrs, drawData.tiles_found); if (drawData.other_models_found) DrawAllBuildings((CELL_OBJECT**)model_object_ptrs, drawData.other_models_found); @@ -1608,4 +1850,4 @@ void GetDLightLevel(SVECTOR* position, u_int* inOutColor) *inOutColor = MIN(lightB, 255) << 16 | MIN(lightG, 255) << 8 | MIN(lightR, 255) | (*inOutColor & 0xFF000000); } -#endif // DYNAMIC_LIGHTING \ No newline at end of file +#endif // DYNAMIC_LIGHTING diff --git a/src_rebuild/Game/C/draw.h b/src_rebuild/Game/C/draw.h index 862974f69..d595bb751 100644 --- a/src_rebuild/Game/C/draw.h +++ b/src_rebuild/Game/C/draw.h @@ -46,6 +46,7 @@ extern MATRIX aspect; extern MATRIX identity; extern MATRIX inv_camera_matrix; extern MATRIX face_camera; +extern MATRIX frustrum_matrix; extern MATRIX2 matrixtable[64]; extern MATRIX2 CompoundMatrix[64]; @@ -85,6 +86,11 @@ extern int pvs_square_sq; extern int PolySizes[56]; extern int setupYet; +extern int current_pvs_cell; +extern int current_pvs_region; +extern int current_pvs_source_region; +extern int current_pvs_loaded_region; +extern int current_pvs_loading_region; extern int combointensity; diff --git a/src_rebuild/Game/C/map.c b/src_rebuild/Game/C/map.c index be17294d9..31d32f0d6 100644 --- a/src_rebuild/Game/C/map.c +++ b/src_rebuild/Game/C/map.c @@ -101,6 +101,11 @@ void ProcessMapLump(char* lump_ptr, int lump_size) view_dist = PVS_CELL_COUNT / 2; pvs_square = PVS_CELL_COUNT; pvs_square_sq = PVS_CELL_COUNT * PVS_CELL_COUNT; + current_pvs_cell = -1; + current_pvs_region = -1; + current_pvs_source_region = -1; + current_pvs_loaded_region = -2; + current_pvs_loading_region = -2; units_across_halved = cells_across / 2 * MAP_CELL_SIZE; units_down_halved = cells_down / 2 * MAP_CELL_SIZE; @@ -167,14 +172,14 @@ int newPositionVisible(VECTOR *pos, char *pvs, int ccx, int ccz) cellz = (dz / MAP_CELL_SIZE) - ccz; #ifndef PSX - cellx = MIN(MAX(cellx, -9), PVS_CELL_COUNT / 2); - cellz = MIN(MAX(cellz, -9), PVS_CELL_COUNT / 2); + cellx = MIN(MAX(cellx, 1 - view_dist), view_dist); + cellz = MIN(MAX(cellz, 1 - view_dist), view_dist); #endif // PSX if (ABS(cellx) <= view_dist && ABS(cellz) <= view_dist) { - return pvs[cellx + 10 + (cellz + 10) * pvs_square] != 0; + return pvs[cellx + view_dist + (cellz + view_dist) * pvs_square] != 0; } return 0; @@ -534,7 +539,9 @@ void PVSDecode(char *output, char *celldata, ushort sz, int havanaCorruptCellBod goto spod; } - sym = ((sym & 3) * 16 + nybblearray[i++]) * 16 + nybblearray[i++ + 1]; + int symMid = nybblearray[i++]; + int symLow = nybblearray[i++]; + sym = ((sym & 3) * 16 + symMid) * 16 + symLow; } pixelIndex += (sym >> 1); @@ -586,7 +593,7 @@ void PVSDecode(char *output, char *celldata, ushort sz, int havanaCorruptCellBod } printf("=========================\n"); #endif - memcpy((u_char*)output, decodebuf, pvs_square_sq-1); // 110*4 + memcpy((u_char*)output, decodebuf, pvs_square_sq); } @@ -638,7 +645,3 @@ void GetPVSRegionCell2(int source_region, int region, int cell, char *output) } } - - - - diff --git a/src_rebuild/Game/C/sky.c b/src_rebuild/Game/C/sky.c index 4ed0c087f..55a4aea42 100644 --- a/src_rebuild/Game/C/sky.c +++ b/src_rebuild/Game/C/sky.c @@ -862,14 +862,14 @@ void PlotSkyPoly(POLYFT4* polys, int skytexnum, unsigned char r, unsigned char g poly->clut = skyclut[skytexnum]; poly->tpage = skytpage[skytexnum]; - addPrim(current->ot + OTSIZE - 1, poly); + addPrim(current->ot + OTSIZE - 1, poly); #if USE_PGXP && USE_EXTENDED_PRIM_POINTERS - poly->pgxp_index = outpoints[src->v0].pgxp_index; + poly->pgxp_index = 0xffff; #endif - current->primptr += sizeof(POLY_FT4); - } + current->primptr += sizeof(POLY_FT4); + } } // [D] [T] @@ -906,14 +906,9 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset, RGB16* skycolor) if(count == 15) gte_stszotz(&z); -#if USE_PGXP - // store PGXP index - // HACK: -1 is needed here for some reason - dv[0].pgxp_index = dv[1].pgxp_index = dv[2].pgxp_index = PGXP_GetIndex(0) - 1; -#endif - dv += 3; - verts += 3; - count -= 3; + dv += 3; + verts += 3; + count -= 3; } while (count); #if USE_PGXP @@ -986,4 +981,3 @@ void DrawSkyDome(void) #endif } - diff --git a/src_rebuild/Game/C/tile.c b/src_rebuild/Game/C/tile.c index 28a647713..4ec4ad9cb 100644 --- a/src_rebuild/Game/C/tile.c +++ b/src_rebuild/Game/C/tile.c @@ -211,11 +211,11 @@ inline int fst_div_3(int x) // [D] [T] -void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) +void DrawTILES(CELL_OBJECT** tiles, int tile_amount) { MODEL* pModel; - PACKED_CELL_OBJECT *ppco; - PACKED_CELL_OBJECT** tilePointers; + CELL_OBJECT *pco; + CELL_OBJECT** tilePointers; int previous_matrix, yang, dofse, Z; int model_number; @@ -255,26 +255,22 @@ void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) plotContext.flags = 0; plotContext.polySizes = PolySizes; - tilePointers = (PACKED_CELL_OBJECT **)tiles; + tilePointers = (CELL_OBJECT **)tiles; while (tile_amount--) { - ppco = *tilePointers++; - - plotContext.scribble[0] = ppco->pos.vx; - plotContext.scribble[1] = (ppco->pos.vy << 0x10) >> 0x11; - plotContext.scribble[2] = ppco->pos.vz; + pco = *tilePointers++; - yang = ppco->value & 0x3f; - model_number = (ppco->value >> 6) | (ppco->pos.vy & 1) << 10; + yang = pco->yang; + model_number = pco->type; if (previous_matrix == yang) { - Z = Apply_InvCameraMatrixSetTrans((VECTOR_NOPAD *)plotContext.scribble); + Z = Apply_InvCameraMatrixSetTrans(&pco->pos); } else { - Z = Apply_InvCameraMatrixAndSetMatrix((VECTOR_NOPAD *)plotContext.scribble, &CompoundMatrix[previous_matrix = yang]); + Z = Apply_InvCameraMatrixAndSetMatrix(&pco->pos, &CompoundMatrix[previous_matrix = yang]); } if (Z <= DRAW_LOD_DIST_HIGH) @@ -706,4 +702,3 @@ void ProcessLowDetailTable(char *lump_ptr, int lump_size) - diff --git a/src_rebuild/Game/C/tile.h b/src_rebuild/Game/C/tile.h index 8c6a927ca..e07cda16c 100644 --- a/src_rebuild/Game/C/tile.h +++ b/src_rebuild/Game/C/tile.h @@ -22,7 +22,7 @@ struct VERTEX u_char pad[2]; }; -extern void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount); // 0x00041D7C +extern void DrawTILES(CELL_OBJECT** tiles, int tile_amount); // 0x00041D7C extern void Tile1x1(MODEL *model); // 0x00041B10 diff --git a/src_rebuild/PsyCross b/src_rebuild/PsyCross index cbb9b8ac7..aeb16b409 160000 --- a/src_rebuild/PsyCross +++ b/src_rebuild/PsyCross @@ -1 +1 @@ -Subproject commit cbb9b8ac7704306157267ffe03a6cac14b9f2332 +Subproject commit aeb16b4091d320aa7a19d5ec11216d0869111b21 From f45145beabe902597d3c2c6b2098bd700d6b1d7b Mon Sep 17 00:00:00 2001 From: Erasmo Bellumat Date: Thu, 30 Apr 2026 20:04:05 -0300 Subject: [PATCH 3/4] CI: add macOS arm64 (Apple Silicon) jobs building both renderers Adds the `macos-sonoma` AppVeyor image to the build matrix and three new scripts that run only on Darwin: Install.macos.sh installs the Homebrew packages for both renderer backends (premake / sdl2 / openal-soft / jpeg + vulkan-headers / vulkan-loader / molten-vk / shaderc), Build.macos.sh runs premake twice (OpenGL default, then --renderer=vulkan) and produces all three configs per renderer, AfterBuild.macos.sh tars the data folder alongside each binary into REDRIVER2_macOS_arm64_{opengl,vulkan}_{Debug,Release,Release_dev}.tar.gz. The existing Linux scripts dispatch to the macOS variants when $(uname -s) == Darwin so the apt/flatpak path doesn't try to run on the macOS image. Adds a `macOS Binaries` artifact group capturing the new tarballs. Windows and Linux jobs are untouched. --- .appveyor/AfterBuild.macos.sh | 15 +++++++++++++++ .appveyor/AfterBuild.sh | 4 ++++ .appveyor/Build.macos.sh | 36 +++++++++++++++++++++++++++++++++++ .appveyor/Build.sh | 4 ++++ .appveyor/Install.macos.sh | 15 +++++++++++++++ .appveyor/Install.sh | 7 +++++++ appveyor.yml | 6 +++++- 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100755 .appveyor/AfterBuild.macos.sh create mode 100755 .appveyor/Build.macos.sh create mode 100755 .appveyor/Install.macos.sh diff --git a/.appveyor/AfterBuild.macos.sh b/.appveyor/AfterBuild.macos.sh new file mode 100755 index 000000000..7907091a5 --- /dev/null +++ b/.appveyor/AfterBuild.macos.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -ex + +cd "$APPVEYOR_BUILD_FOLDER/src_rebuild" + +for renderer in opengl vulkan +do + for config in Debug Release Release_dev + do + cd "$APPVEYOR_BUILD_FOLDER/src_rebuild/bin/macos_arm64_${renderer}/${config}" + cp -R "$APPVEYOR_BUILD_FOLDER/data/"* ./ + tar -czf "REDRIVER2_macOS_arm64_${renderer}_${config}.tar.gz" * + mv "REDRIVER2_macOS_arm64_${renderer}_${config}.tar.gz" "$APPVEYOR_BUILD_FOLDER/src_rebuild/bin/${config}/" + done +done diff --git a/.appveyor/AfterBuild.sh b/.appveyor/AfterBuild.sh index 942b83fc5..cf9fc58f7 100755 --- a/.appveyor/AfterBuild.sh +++ b/.appveyor/AfterBuild.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -ex +if [ "$(uname -s)" = "Darwin" ]; then + exec "${APPVEYOR_BUILD_FOLDER}/.appveyor/AfterBuild.macos.sh" +fi + for config in debug release release_dev do cd "${APPVEYOR_BUILD_FOLDER}/src_rebuild/bin/${config^}" diff --git a/.appveyor/Build.macos.sh b/.appveyor/Build.macos.sh new file mode 100755 index 000000000..5f251b112 --- /dev/null +++ b/.appveyor/Build.macos.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -ex + +cd "$APPVEYOR_BUILD_FOLDER/src_rebuild" + +# Strip any cached project files between renderer variants so premake +# regenerates the makefile with the right --renderer option. +rm -rf project_gmake_macosx + +NCPU=$(sysctl -n hw.ncpu) + +# ---------- OpenGL build (default) ---------- +premake5 gmake +pushd project_gmake_macosx > /dev/null +for config in debug_arm64 release_arm64 release_dev_arm64 +do + make config=$config -j$NCPU +done +popd > /dev/null + +# Stash the OpenGL artifacts before regenerating for Vulkan. +mkdir -p bin/macos_arm64_opengl +cp -R bin/Debug bin/Release bin/Release_dev bin/macos_arm64_opengl/ + +# ---------- Vulkan / MoltenVK build ---------- +rm -rf project_gmake_macosx +premake5 --renderer=vulkan gmake +pushd project_gmake_macosx > /dev/null +for config in debug_arm64 release_arm64 release_dev_arm64 +do + make config=$config -j$NCPU +done +popd > /dev/null + +mkdir -p bin/macos_arm64_vulkan +cp -R bin/Debug bin/Release bin/Release_dev bin/macos_arm64_vulkan/ diff --git a/.appveyor/Build.sh b/.appveyor/Build.sh index a0efff856..d3e2df0af 100755 --- a/.appveyor/Build.sh +++ b/.appveyor/Build.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -ex +if [ "$(uname -s)" = "Darwin" ]; then + exec "${APPVEYOR_BUILD_FOLDER}/.appveyor/Build.macos.sh" +fi + # Configure cd "$APPVEYOR_BUILD_FOLDER/src_rebuild" ./premake5 gmake2 diff --git a/.appveyor/Install.macos.sh b/.appveyor/Install.macos.sh new file mode 100755 index 000000000..780f96ab4 --- /dev/null +++ b/.appveyor/Install.macos.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -ex + +# AppVeyor's macOS image ships Homebrew but not the package set we need. +# This installs everything required to build both renderer backends — +# OpenGL (default) and Vulkan/MoltenVK (opt-in via --renderer=vulkan). + +brew update + +# Common deps for both backends +brew install premake sdl2 jpeg openal-soft + +# Vulkan backend deps. shaderc is statically linked from libshaderc_combined.a +# at link time so the produced binary doesn't need a runtime SPIR-V compiler. +brew install vulkan-headers vulkan-loader molten-vk shaderc diff --git a/.appveyor/Install.sh b/.appveyor/Install.sh index 7ca639f0b..929f6427f 100755 --- a/.appveyor/Install.sh +++ b/.appveyor/Install.sh @@ -1,6 +1,13 @@ #!/usr/bin/env bash set -ex +# AppVeyor runs `sh:` steps on every non-Windows image. Delegate to a +# macOS-specific script when the worker is macOS so the Linux-only +# apt/flatpak path below doesn't try to run. +if [ "$(uname -s)" = "Darwin" ]; then + exec "${APPVEYOR_BUILD_FOLDER}/.appveyor/Install.macos.sh" +fi + cd "$APPVEYOR_BUILD_FOLDER/src_rebuild" # Download premake5 diff --git a/appveyor.yml b/appveyor.yml index 876327b53..534632bcb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ skip_tags: true image: - Visual Studio 2019 - Ubuntu2004 +- macos-sonoma environment: data_folder: '%APPVEYOR_BUILD_FOLDER%\data' @@ -50,6 +51,9 @@ artifacts: - path: src_rebuild\bin\*\*.tar.gz name: Linux Binaries - + - path: io.github.opendriver2.Redriver2.flatpak name: Linux Flatpak + + - path: src_rebuild/bin/*/REDRIVER2_macOS_arm64_*.tar.gz + name: macOS Binaries From 5aab00a494d24d66e1c7b07b00f0a35f16925219 Mon Sep 17 00:00:00 2001 From: Erasmo Bellumat Date: Thu, 30 Apr 2026 20:24:13 -0300 Subject: [PATCH 4/4] README: add credit for macOS arm64 / Vulkan port --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2349b5e86..8d08b1363 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,4 @@ See [Contributing to project](https://github.com/OpenDriver2/REDRIVER2/wiki/Cont - **Gh0stBlade** - HLE Emulator code used as a base for Psy-Cross [(link)](https://github.com/TOMB5/TOMB5/tree/master/EMULATOR) - **Ben Lincoln** - [This Dust Remembers What It Once Was](https://www.beneaththewaves.net/Software/This_Dust_Remembers_What_It_Once_Was.html) (*TDR*) - **Stohrendorf** - [Symdump](https://github.com/stohrendorf/symdump) utility +- **ebellumat** - macOS Apple Silicon (arm64) port, Vulkan / MoltenVK backend