Skip to content

Commit 542e51d

Browse files
committed
macOS Apple Silicon arm64 + Vulkan/MoltenVK backend (WIP)
Brings PsyCross up on Apple Silicon and adds an opt-in Vulkan/MoltenVK rendering backend alongside the existing OpenGL one. Selected at build time via the parent project's premake option `--renderer=vulkan`. Visible state with the Vulkan backend enabled: - Driver 2 main menu (logo, items, sepia tint, red highlight) renders pixel-equivalent to the OpenGL backend. - HUD (Damage / Felony bars, timer, mini-map) renders with the right palette. - 3D world geometry is visible (skyline, skybox, player car segments). Wet-street reflection still wins over the road overlay until blend modes are re-enabled (gated for now — see below). - Texture sampling for 4-bit / 8-bit / 16-bit paletted PSX textures with CLUT lookup; UVs floor()ed to integer texels to avoid sub-texel palette-index noise; RGB-1555 decode. - D32_SFLOAT depth attachment with depth test (LESS_OR_EQUAL); the GL-style projection matrices are remapped from [-1, 1] to Vulkan's [0, 1] z range in the vertex shader. Build / toolchain: - src/render/PsyX_render_vk.cpp — new Vulkan backend behind RENDERER_VK. Vulkan instance + surface + device + swapchain + render pass + command buffers, single graphics pipeline layout matching GrVertex (PGXP variant), push-constant projection (2D ortho + 3D perspective), shaderc-runtime GLSL→SPIR-V compile, GR_BeginScene/EndScene/Clear/ DrawTriangles/UpdateVertexBuffer plus stubs for the rest of the GR_* surface. - include/PsyX/PsyX_render.h — recognises RENDERER_VK alongside the existing RENDERER_OGL/RENDERER_OGLES paths. - 5 PSX BlendMode pipeline variants (BM_NONE / BM_AVERAGE / BM_ADD / BM_SUBTRACT / BM_ADD_QUATER_SOURCE) pre-baked; GR_SetBlendMode picks the active one. Currently forced to BM_NONE because BM_AVERAGE was being applied to every semi-trans face and turning the entire HUD translucent — re-enable will need the texture's STP bit plumbed through to the fragment first. LP64 / arm64 portability fixes: - include/psx/types.h: u_long → uint32_t on LP64 (preserves PSX 32-bit semantics) with a macOS guard that pre-empts <sys/types.h> typedefs. - include/psx/libgpu.h: P_LEN = 3 for arm64 (same as x86_64) and a 64-bit-correct pointer pack. - src/psx/LIBCD.C, LIBETC.C, LIBGPU.C, LIBSPU.C: macOS portability — malloc.h guards, intptr_t casts for ptr→int conversions, missing libetc include. - src/PsyX_main.cpp: drop the intrThread, run vsync_callback on the main thread instead. Fixes an arm64 race where the interrupt thread could fire vsync_callback in the middle of OT building, dropping primitives non-deterministically. - src/render/PsyX_render.cpp: disable async PBO/blit on macOS — the GL-on-Metal compatibility layer doesn't honour the same timing the GL backend assumes.
1 parent 9a4811d commit 542e51d

14 files changed

Lines changed: 1925 additions & 125 deletions

File tree

include/PsyX/PsyX_render.h

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55

66
/*
77
* Platform specific emulator setup
8+
*
9+
* RENDERER_VK can be defined externally (via build system) to opt into
10+
* the Vulkan/MoltenVK backend instead of the default OpenGL one.
811
*/
9-
#if (defined(_WIN32) || defined(__APPLE__) || defined(__linux__)) && !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) && !defined(__RPI__)
12+
#if defined(RENDERER_VK)
13+
/* Vulkan backend selected — keep RENDERER_OGL/OGLES undefined */
14+
#elif (defined(_WIN32) || defined(__APPLE__) || defined(__linux__)) && !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) && !defined(__RPI__)
1015
# define RENDERER_OGL
1116
# define USE_GLAD
1217
#elif defined(__RPI__)
@@ -26,6 +31,14 @@
2631
# define USE_OPENGL 0
2732
#endif
2833

34+
#if defined(RENDERER_VK)
35+
# define USE_VULKAN 1
36+
#else
37+
# ifndef USE_VULKAN
38+
# define USE_VULKAN 0
39+
# endif
40+
#endif
41+
2942
#if OGLES_VERSION == 2
3043
# define ES2_SHADERS
3144
#elif OGLES_VERSION == 3
@@ -35,22 +48,15 @@
3548
/*
3649
* OpenGL
3750
*/
38-
3951
#if defined (RENDERER_OGL)
4052

4153
# define GL_GLEXT_PROTOTYPES
42-
43-
#if defined(USE_GLAD)
4454
# include "common/glad.h"
45-
#endif
4655

4756
#elif defined (RENDERER_OGLES)
4857

4958
# define GL_GLEXT_PROTOTYPES
5059

51-
#if defined(USE_GLAD)
52-
# include "common/glad.h"
53-
#else
5460
# ifdef __EMSCRIPTEN__
5561
# include <GL/gl.h>
5662
# else
@@ -61,7 +67,6 @@
6167
# include <GLES3/gl3.h>
6268
# endif
6369
# endif
64-
#endif
6570

6671
# include <EGL/egl.h>
6772

@@ -72,6 +77,9 @@
7277
# define TEXTURE_FORMAT GL_UNSIGNED_SHORT_1_5_5_5_REV
7378
#elif defined(RENDERER_OGLES)
7479
# define TEXTURE_FORMAT GL_UNSIGNED_SHORT_5_5_5_1
80+
#elif defined(RENDERER_VK)
81+
/* Vulkan equivalent: VK_FORMAT_A1R5G5B5_UNORM_PACK16. Symbolic constant
82+
* resolved in the Vulkan backend; nothing to define here for the public header. */
7583
#endif
7684

7785
#include "psx/types.h"
@@ -105,6 +113,8 @@
105113
#elif defined(RENDERER_OGLES)
106114
# define VRAM_FORMAT GL_LUMINANCE_ALPHA
107115
# define VRAM_INTERNAL_FORMAT GL_LUMINANCE_ALPHA
116+
#elif defined(RENDERER_VK)
117+
/* Vulkan: VRAM is emulated as a R32G32_SFLOAT image (analogous to GL_RG32F) */
108118
#endif
109119

110120
#define VRAM_WIDTH (1024)
@@ -163,8 +173,13 @@ typedef enum
163173
#if defined(RENDERER_OGLES) || defined(RENDERER_OGL)
164174
typedef uint TextureID;
165175
typedef uint ShaderID;
176+
#elif defined(RENDERER_VK)
177+
/* Vulkan: opaque uint32_t handles into internal Vulkan resource tables.
178+
* Backend manages VkImage/VkImageView/VkSampler/VkPipeline creation. */
179+
typedef uint TextureID;
180+
typedef uint ShaderID;
166181
#else
167-
#error
182+
#error "No renderer backend selected"
168183
#endif
169184

170185
#if defined(_LANGUAGE_C_PLUS_PLUS)||defined(__cplusplus)||defined(c_plusplus)

include/psx/inline_c.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ extern int doCOP2(int op);
104104
MTC2(*(uint*)((char*)(r2)), 22); \
105105
MTC2(*(uint*)((char*)(r2)), 6); }
106106

107-
// mtc2 12, lwc2 1
107+
// mtc2 0, lwc2 1
108108
#define gte_ldlv0( r0 ) \
109-
{ MTC2((*(ushort*)((char*)(r0)+4) << 16) | *(ushort*)((char*)(r0)), 12);\
110-
MTC2(*(ushort*)((char*)(r0)+8), 1); }
109+
{ MTC2((*(ushort*)((char*)(r0)+4) << 16) | *(ushort*)((char*)(r0)));\
110+
MTC2(*(ushort*)((char*)(r0)+8) << 16); }
111111

112112
// mtc2 8
113113
#define gte_lddp( r0 ) \

include/psx/libgpu.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,14 +329,14 @@ typedef struct _RECT16 {
329329

330330
#if USE_EXTENDED_PRIM_POINTERS
331331

332-
#if defined(_M_X64) || defined(__amd64__)
332+
#if defined(_M_X64) || defined(__amd64__) || defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)
333333

334334
#define DECLARE_P_ADDR \
335335
uintptr_t addr; \
336336
uint len : 16; \
337337
uint pgxp_index : 16;
338338

339-
#define P_LEN 3 // 3 longs
339+
#define P_LEN 3 // 3 longs (addr is 8 bytes on 64-bit + 4 bytes len/pgxp_index)
340340

341341
#else
342342

@@ -347,7 +347,7 @@ typedef struct _RECT16 {
347347

348348
#define P_LEN 2 // 2 longs
349349

350-
#endif // _M_X64 || __amd64__
350+
#endif // 64-bit detection
351351

352352
#define DECLARE_P_ADDR_PTAG DECLARE_P_ADDR
353353

include/psx/libgte.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ SVECTOR *ApplyMatrixSV(MATRIX *m, SVECTOR *v0, SVECTOR *v1);
4848
VECTOR *ApplyMatrixLV(MATRIX *m, VECTOR *v0, VECTOR *v1);
4949
extern void RotTrans(SVECTOR* v0, VECTOR* v1, long* flag);
5050
extern void RotTransSV(SVECTOR* v0, SVECTOR* v1, long* flag);
51-
extern int RotTransPers(SVECTOR* v0, int* sxy, long* p, long* flag);
51+
extern int RotTransPers(SVECTOR* v0, long* sxy, long* p, long* flag);
5252
extern int RotTransPers3(SVECTOR* v0, SVECTOR* v1, SVECTOR* v2, long* sxy0, long* sxy1, long* sxy2, long* p, long* flag);
5353
extern int RotTransPers4(SVECTOR* v0, SVECTOR* v1, SVECTOR* v2, SVECTOR* v3, long* sxy0, long* sxy1, long* sxy2, long* sxy3, long* p, long* flag);
5454
extern void NormalColor(SVECTOR* v0, CVECTOR* v1);

include/psx/types.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@
44
#include <stdint.h>
55
#include <stddef.h>
66

7+
/*
8+
* Pre-empt macOS/BSD <sys/types.h> from declaring u_long as `unsigned long`
9+
* (which is 64-bit on LP64). PSX code expects u_long to be 32-bit, so we
10+
* mark the system's guard and define u_long ourselves below.
11+
*/
12+
#if defined(__APPLE__) && (defined(__LP64__) || defined(_LP64))
13+
# ifndef _U_LONG
14+
# define _U_LONG
15+
# endif
16+
# ifndef _U_INT
17+
# define _U_INT
18+
# endif
19+
# ifndef _U_CHAR
20+
# define _U_CHAR
21+
# endif
22+
# ifndef _U_SHORT
23+
# define _U_SHORT
24+
# endif
25+
#endif
26+
727
#if !defined(__APPLE__)
828
/* major part of a device */
929
#define major(x) ((int)(((unsigned)(x)>>8)&0377))
@@ -30,8 +50,18 @@ typedef unsigned int u_int;
3050
#endif
3151
#ifndef _ULONG_T
3252
#define _ULONG_T
53+
/*
54+
* The original PSX SDK treats `u_long` as a 32-bit type. On LP64 platforms
55+
* (macOS arm64/x64, Linux x64) `unsigned long` is 64-bit, which breaks the
56+
* PSX-style packing/casting throughout the codebase. Map it to uint32_t on
57+
* those targets to preserve binary layout and call-site compatibility.
58+
*/
59+
#if defined(__LP64__) || defined(_LP64)
60+
typedef uint32_t u_long;
61+
#else
3362
typedef unsigned long u_long;
3463
#endif
64+
#endif
3565
#ifndef _SYSIII_USHORT
3666
#define _SYSIII_USHORT
3767
typedef unsigned short ushort; /* sys III compat */
@@ -43,10 +73,16 @@ typedef unsigned int uint; /* sys V compat */
4373
#endif
4474
#ifndef _SYSV_ULONG
4575
#define _SYSV_ULONG
76+
#if defined(__LP64__) || defined(_LP64)
77+
typedef uint32_t ulong; /* sys V compat */
78+
#else
4679
typedef unsigned long ulong; /* sys V compat */
4780
#endif
81+
#endif
4882
#endif /* ! __psx__ */
4983

84+
#ifndef NBBY
5085
#define NBBY 8
86+
#endif
5187

5288
#endif

src/PsyX_main.cpp

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -146,71 +146,66 @@ int PsyX_Sys_SetVMode(int mode)
146146
return old;
147147
}
148148

149+
// Drives the vsync_callback + vblank counter from the calling thread.
150+
// Originally this was done from a separate `intrThreadMain` thread, but
151+
// firing PSX-VBlank callbacks asynchronously while the main thread was in
152+
// the middle of building the OT / running game logic raced badly on
153+
// arm64 (LP64 + weaker memory ordering than x86), producing intermittently
154+
// missing geometry. Doing it inline here keeps everything single-threaded.
155+
static void PsyX_PollVBlankFromMainThread(void)
156+
{
157+
const double timestep = g_vmode == MODE_NTSC ? FIXED_TIME_STEP_NTSC : FIXED_TIME_STEP_PAL;
158+
159+
// Catch up: fire one callback per missed timestep
160+
while (1)
161+
{
162+
const double vblDelta = Util_GetHPCTime(&g_vblTimer, 0);
163+
if (vblDelta < timestep)
164+
break;
165+
166+
if (vsync_callback)
167+
vsync_callback();
168+
169+
g_psxSysCounters[PsxCounter_VBLANK]++;
170+
Util_GetHPCTime(&g_vblTimer, 1);
171+
}
172+
}
173+
149174
int PsyX_Sys_GetVBlankCount()
150175
{
176+
PsyX_PollVBlankFromMainThread();
177+
151178
if (g_skipSwapInterval)
152179
{
153180
// extra speedup.
154181
// does not affect `vsync_callback` count
155182
g_psxSysCounters[PsxCounter_VBLANK] += 1;
156183
g_frameSkip++;
157184
}
158-
185+
159186
return g_psxSysCounters[PsxCounter_VBLANK];
160187
}
161188

189+
// Kept as a no-op stub so any external callers / port code still link.
190+
// The real logic now lives in PsyX_PollVBlankFromMainThread() and runs
191+
// on the main thread via PsyX_Sys_GetVBlankCount().
162192
int intrThreadMain(void* data)
163193
{
164-
Util_InitHPCTimer(&g_vblTimer);
165-
166-
while (!g_stopIntrThread)
167-
{
168-
// step counters
169-
{
170-
const double timestep = g_vmode == MODE_NTSC ? FIXED_TIME_STEP_NTSC : FIXED_TIME_STEP_PAL;
171-
const double vblDelta = Util_GetHPCTime(&g_vblTimer, 0);
172-
173-
if (vblDelta > timestep)
174-
{
175-
SDL_LockMutex(g_intrMutex);
176-
177-
if (vsync_callback)
178-
vsync_callback();
179-
180-
SDL_UnlockMutex(g_intrMutex);
181-
182-
// do vblank events
183-
g_psxSysCounters[PsxCounter_VBLANK]++;
184-
185-
Util_GetHPCTime(&g_vblTimer, 1);
186-
}
187-
188-
}
189-
}
190-
194+
(void)data;
191195
return 0;
192196
}
193197

194198
static int PsyX_Sys_InitialiseCore()
195199
{
196-
#ifdef __EMSCRIPTEN__
200+
// Initialise the timer on whichever thread will be polling vblank
201+
// (always the main thread now).
197202
Util_InitHPCTimer(&g_vblTimer);
198-
#else
199203

200-
g_intrThread = SDL_CreateThread(intrThreadMain, "psyX_intr", NULL);
201-
202-
if (NULL == g_intrThread)
203-
{
204-
eprinterr("SDL_CreateThread failed: %s\n", SDL_GetError());
205-
return 0;
206-
}
207-
208-
g_intrMutex = SDL_CreateMutex();
209-
if (NULL == g_intrMutex)
210-
{
211-
eprinterr("SDL_CreateMutex failed: %s\n", SDL_GetError());
212-
return 0;
213-
}
204+
#ifndef __EMSCRIPTEN__
205+
// We no longer spawn `psyX_intr`. The mutex is left null because no
206+
// other thread takes it; any legacy locking sites are guarded.
207+
g_intrThread = NULL;
208+
g_intrMutex = NULL;
214209
#endif
215210
return 1;
216211
}

0 commit comments

Comments
 (0)