Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions build_framework.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@echo off
REM Build the psxrecomp FRAMEWORK (recompiler tool + BIOS-only runtime).
REM Uses MSVC from VS Build Tools 2026 and its bundled CMake + Ninja (no PATH setup required).

set "VSBT=C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools"
call "%VSBT%\VC\Auxiliary\Build\vcvars64.bat" || exit /b 1
set "PATH=%VSBT%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin;%VSBT%\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja;%PATH%"

cd /d D:\tools\psx-recomp || exit /b 1

echo ===== [1/4] Configure recompiler =====
cmake -S recompiler -B recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release || exit /b 1
echo ===== [2/4] Build recompiler =====
cmake --build recompiler/build || exit /b 1
echo ===== [3/4] Configure runtime =====
REM MSVC portability flags:
REM /DNOMINMAX - stop windows.h min/max macros clobbering std::min/std::max
REM /EHsc - C++ exception unwind semantics (restored after CXX_FLAGS override)
REM /std:c11 - the runtime's C files use <stdatomic.h> (C11 atomics)
REM /experimental:c11atomics - MSVC gates <stdatomic.h> behind this opt-in
cmake -S runtime -B runtime/build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="/DNOMINMAX /EHsc" -DCMAKE_C_FLAGS="/DNOMINMAX /std:c11 /experimental:c11atomics" || exit /b 1
echo ===== [4/4] Build runtime (psx-runtime) =====
cmake --build runtime/build --target psx-runtime || exit /b 1

echo === BUILD OK ===
10 changes: 9 additions & 1 deletion recompiler/src/full_function_emitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1656,9 +1656,17 @@ void FullFunctionEmitter::emit_dispatch(
// RECURSION_BUG.md §25 — mark CPS mode at startup for runtime code that
// must emit the CPS contract (the overlay sljit JIT, overlay_sljit.c).
out += "\n/* CPS runtime-mode marker (overlay sljit JIT reads g_psx_cps_mode). */\n";
out += "__attribute__((constructor)) static void psx_cps_mark_bios(void) {\n";
out += "static void psx_cps_mark_bios(void) {\n";
out += " extern int g_psx_cps_mode; g_psx_cps_mode = 1;\n";
out += "}\n";
// Run psx_cps_mark_bios before main(). __attribute__((constructor)) is
// GCC/Clang-only; MSVC uses a static initializer pointer in .CRT$XCU.
out += "#if defined(_MSC_VER)\n";
out += "#pragma section(\".CRT$XCU\", read)\n";
out += "__declspec(allocate(\".CRT$XCU\")) static void (*psx_cps_mark_bios_ctor)(void) = psx_cps_mark_bios;\n";
out += "#else\n";
out += "__attribute__((constructor)) static void psx_cps_mark_bios_ctor(void) { psx_cps_mark_bios(); }\n";
out += "#endif\n";
}
}

Expand Down
10 changes: 9 additions & 1 deletion recompiler/src/main_psx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1006,9 +1006,17 @@ int main(int argc, char** argv) {
// JIT (overlay_sljit.c) emits the CPS contract. Static ctor: no clash
// with the BIOS dispatch's marker.
ds << "\n/* CPS runtime-mode marker (overlay sljit JIT reads g_psx_cps_mode). */\n";
ds << "__attribute__((constructor)) static void psx_cps_mark_game(void) {\n";
ds << "static void psx_cps_mark_game(void) {\n";
ds << " extern int g_psx_cps_mode; g_psx_cps_mode = 1;\n";
ds << "}\n";
// Run psx_cps_mark_game before main(). __attribute__((constructor)) is
// GCC/Clang-only; MSVC uses a static initializer pointer in .CRT$XCU.
ds << "#if defined(_MSC_VER)\n";
ds << "#pragma section(\".CRT$XCU\", read)\n";
ds << "__declspec(allocate(\".CRT$XCU\")) static void (*psx_cps_mark_game_ctor)(void) = psx_cps_mark_game;\n";
ds << "#else\n";
ds << "__attribute__((constructor)) static void psx_cps_mark_game_ctor(void) { psx_cps_mark_game(); }\n";
ds << "#endif\n";
}

std::ofstream dispatch_file(dispatch_filename);
Expand Down
24 changes: 24 additions & 0 deletions regen_all.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@echo off
REM Rebuild recompiler with the portable-constructor emitter fix, regenerate the
REM BIOS + game C, and rebuild the framework runtime. Run after editing the emitter.

set "VSBT=C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools"
call "%VSBT%\VC\Auxiliary\Build\vcvars64.bat" || exit /b 1
set "PATH=%VSBT%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin;%VSBT%\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja;D:\DEV_TOOLS\Git\bin;%PATH%"

cd /d D:\tools\psx-recomp || exit /b 1
echo ===== [1/4] Rebuild recompiler (emitter fix) =====
cmake --build recompiler/build || exit /b 1

echo ===== [2/4] Regenerate BIOS C =====
bash tools/regen_bios.sh || exit /b 1

echo ===== [3/4] Regenerate SmackDown game C =====
cd /d D:\tools\SmackDown2Recomp || exit /b 1
"D:\tools\psx-recomp\recompiler\build\psxrecomp-game.exe" --config game.toml || exit /b 1

echo ===== [4/4] Rebuild framework runtime (BIOS) =====
cd /d D:\tools\psx-recomp || exit /b 1
cmake --build runtime/build --target psx-runtime || exit /b 1

echo === REGEN + RUNTIME OK ===
19 changes: 19 additions & 0 deletions regen_game_build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@echo off
REM Rebuild recompiler (game emitter fix), regenerate SmackDown game C, rebuild game.

set "VSBT=C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools"
call "%VSBT%\VC\Auxiliary\Build\vcvars64.bat" || exit /b 1
set "PATH=%VSBT%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin;%VSBT%\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja;%PATH%"

cd /d D:\tools\psx-recomp || exit /b 1
echo ===== [1/3] Rebuild recompiler (game emitter fix) =====
cmake --build recompiler/build || exit /b 1

echo ===== [2/3] Regenerate SmackDown game C =====
cd /d D:\tools\SmackDown2Recomp || exit /b 1
"D:\tools\psx-recomp\recompiler\build\psxrecomp-game.exe" --config game.toml || exit /b 1

echo ===== [3/3] Build SmackDown2Recomp =====
cmake --build build --target psx-runtime || exit /b 1

echo === GAME BUILD OK ===
12 changes: 9 additions & 3 deletions runtime/include/gpu_vk_renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,17 @@ void vk_renderer_present_blank(void);
* server, 24-bit present). No-op when the Vulkan path is inactive. */
void vk_renderer_sync_cpu(void);

/* Set the present mode: 1=FIFO (vsync), 0=IMMEDIATE (lowest latency, may tear),
* -1=MAILBOX (low-latency, tear-free). Applied on the next swapchain (re)build;
* unsupported modes fall back to FIFO (always available). */
/* Set the present policy: 1=tear-free, 0=IMMEDIATE (lowest latency, may tear),
* -1=MAILBOX. Tear-free prefers MAILBOX because the frontend already paces
* frames; unsupported modes fall back to FIFO (always available). */
void vk_renderer_set_present_mode(int mode);

/* Screen simulation for the Vulkan present path (ScreenKind values from
* color_lut.h: 0 raw, 1 crt, 2 composite, 3 trinitron). Raw keeps the exact
* present blit; the others route the 15-bit present through a CRT shader
* pass (PSX_SCREEN env overrides, same as the software path). */
void vk_renderer_set_screen_kind(int kind);

#ifdef __cplusplus
}
#endif
Expand Down
9 changes: 9 additions & 0 deletions runtime/include/psx_cyc.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#define PSX_CYC_H

#include <stdint.h>
#if defined(_MSC_VER)
#include <intrin.h> /* MSVC intrinsics: _BitScanForward (no __builtin_ctz) */
#endif
#include "cpu_state.h" /* CPUState (guard-safe: cpu_state.h includes us last) */

#ifdef __cplusplus
Expand All @@ -49,7 +52,13 @@ static inline void psx_cyc_base(CPUState* cpu) {
static inline void psx_cyc_deps(CPUState* cpu, uint32_t reg_mask) {
reg_mask &= 0xFFFFFFFEu; /* never touch ReadAbsorb[0] */
while (reg_mask) {
#if defined(_MSC_VER)
unsigned long _psx_ctz_idx;
_BitScanForward(&_psx_ctz_idx, reg_mask);
unsigned n = (unsigned)_psx_ctz_idx;
#else
unsigned n = (unsigned)__builtin_ctz(reg_mask);
#endif
cpu->read_absorb[n] = 0u;
reg_mask &= reg_mask - 1u;
}
Expand Down
75 changes: 75 additions & 0 deletions runtime/include/spu_gauss.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* spu_gauss.h - PS1 SPU hardware Gaussian interpolation table (512 entries).
* Values from No$PSX docs / DuckStation core/spu.cpp (SPU::Voice::Interpolate).
* Usage, with i = (phase >> 4) & 0xFF and s[0]=current, s[-1..-3]=history:
* out = (g[0x0FF-i]*s[-3] + g[0x1FF-i]*s[-2] + g[0x100+i]*s[-1] + g[i]*s[0]) >> 15
*/
#ifndef PSX_SPU_GAUSS_H
#define PSX_SPU_GAUSS_H
#include <stdint.h>
static const int16_t spu_gauss_table[512] = {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 2, 2, 2, 3, 3,
3, 4, 4, 5, 5, 6, 7, 7,
8, 9, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 21, 22, 24,
25, 27, 28, 30, 32, 33, 35, 37,
39, 41, 44, 46, 48, 51, 53, 56,
58, 61, 64, 67, 70, 73, 77, 80,
84, 87, 91, 95, 99, 103, 107, 111,
116, 120, 125, 130, 135, 140, 145, 150,
156, 161, 167, 173, 179, 186, 192, 199,
205, 212, 219, 227, 234, 242, 250, 257,
266, 274, 283, 291, 300, 309, 319, 328,
338, 348, 358, 369, 379, 390, 401, 412,
424, 436, 448, 460, 473, 485, 498, 512,
525, 539, 553, 567, 582, 597, 612, 627,
643, 659, 675, 692, 708, 726, 743, 761,
779, 797, 816, 835, 854, 874, 894, 914,
935, 956, 977, 999, 1020, 1043, 1066, 1089,
1112, 1136, 1160, 1184, 1209, 1234, 1260, 1286,
1312, 1339, 1366, 1394, 1422, 1450, 1479, 1508,
1537, 1567, 1598, 1628, 1660, 1691, 1723, 1756,
1789, 1822, 1856, 1890, 1924, 1959, 1995, 2031,
2067, 2104, 2141, 2179, 2217, 2256, 2295, 2334,
2374, 2415, 2456, 2497, 2539, 2582, 2624, 2668,
2712, 2756, 2801, 2846, 2892, 2938, 2985, 3032,
3079, 3128, 3176, 3225, 3275, 3325, 3376, 3427,
3479, 3531, 3584, 3637, 3691, 3745, 3799, 3855,
3910, 3967, 4023, 4081, 4138, 4197, 4255, 4315,
4374, 4435, 4495, 4557, 4619, 4681, 4744, 4807,
4871, 4935, 5000, 5065, 5131, 5197, 5264, 5332,
5399, 5468, 5536, 5606, 5676, 5746, 5817, 5888,
5959, 6032, 6104, 6177, 6251, 6325, 6400, 6475,
6550, 6626, 6702, 6779, 6856, 6934, 7012, 7091,
7170, 7249, 7329, 7409, 7490, 7571, 7653, 7735,
7817, 7900, 7983, 8066, 8150, 8234, 8319, 8404,
8489, 8575, 8661, 8748, 8834, 8922, 9009, 9097,
9185, 9273, 9362, 9451, 9541, 9630, 9720, 9811,
9901, 9992, 10083, 10174, 10266, 10358, 10450, 10542,
10635, 10727, 10820, 10913, 11007, 11100, 11194, 11288,
11382, 11476, 11571, 11665, 11760, 11855, 11950, 12045,
12140, 12236, 12331, 12427, 12522, 12618, 12714, 12809,
12905, 13001, 13097, 13193, 13289, 13385, 13481, 13577,
13673, 13769, 13865, 13961, 14056, 14152, 14248, 14343,
14439, 14534, 14630, 14725, 14820, 14915, 15010, 15104,
15199, 15293, 15387, 15481, 15575, 15669, 15762, 15855,
15948, 16041, 16133, 16226, 16317, 16409, 16500, 16592,
16682, 16773, 16863, 16953, 17042, 17131, 17220, 17308,
17396, 17484, 17571, 17658, 17744, 17830, 17916, 18001,
18086, 18170, 18254, 18337, 18420, 18502, 18584, 18665,
18746, 18826, 18905, 18985, 19063, 19141, 19219, 19295,
19372, 19447, 19522, 19597, 19671, 19744, 19816, 19888,
19959, 20030, 20100, 20169, 20238, 20306, 20373, 20439,
20505, 20570, 20634, 20698, 20760, 20822, 20884, 20944,
21004, 21063, 21121, 21178, 21235, 21290, 21345, 21399,
21452, 21505, 21556, 21607, 21657, 21706, 21754, 21801,
21848, 21893, 21938, 21982, 22025, 22066, 22107, 22148,
22187, 22225, 22262, 22299, 22334, 22369, 22402, 22435,
22467, 22498, 22527, 22556, 22584, 22611, 22637, 22662,
22686, 22709, 22731, 22752, 22772, 22791, 22809, 22826,
22842, 22857, 22872, 22885, 22897, 22908, 22918, 22927,
22935, 22942, 22948, 22953, 22957, 22960, 22962, 22963,
};
#endif /* PSX_SPU_GAUSS_H */
6 changes: 3 additions & 3 deletions runtime/launcher/launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class LauncherSystemInterface : public SystemInterface_SDL {

// Mirror of the user-tunable settings, in the value shapes the RML binds to.
struct LauncherModel {
int renderer = 0; // 0=software, 1=opengl
int renderer = 0; // 0=software, 1=opengl, 2=vulkan
int supersampling = 1; // 1..4
bool antialiasing = true;
int texture_filter = 0; // 0=nearest, 1=bilinear
Expand Down Expand Up @@ -381,7 +381,7 @@ int lang_index_for(const std::vector<psx_launcher::GameInfo::Language>& langs,
return 0; // unknown/first — the game's declared default sits at [0] by convention
}

const char* renderer_name(int v) { return v == 1 ? "OpenGL" : "Software"; }
const char* renderer_name(int v) { return v == 2 ? "Vulkan" : v == 1 ? "OpenGL" : "Software"; }
const char* texfilter_name(int v) { return v == 1 ? "Bilinear" : "Nearest"; }
const char* crt_name(int v) {
switch (v) {
Expand Down Expand Up @@ -948,7 +948,7 @@ Result run(SDL_Window* window, void* gl_context,

c.BindEventCallback("cycle_renderer",
[&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable {
m.renderer ^= 1;
m.renderer = (m.renderer + 1) % 3;
refresh_labels(m);
handle.DirtyVariable("renderer_label");
handle.DirtyVariable("opengl_renderer");
Expand Down
82 changes: 82 additions & 0 deletions runtime/shaders/crt.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#version 450
/* CRT present shader (CRT-Royale-inspired, single pass). Replaces the plain
* present blit when screen kind != raw. Sampled in linear filtering.
*
* Kinds (must match ScreenKind in color_lut.h):
* 1 crt shadow-mask triads + gaussian scanline beam
* 2 composite strong horizontal blur + chroma bleed + soft scanlines
* 3 trinitron aperture-grille stripes + gaussian scanline beam
*
* Royale signature bits kept: gamma-correct pipeline, energy-conserving
* gaussian beam whose width grows with brightness (bright lines bloom),
* phosphor mask applied in linear light with gain compensation, and the
* scanline effect fading out when the output has too few pixels per line
* to resolve it (instead of aliasing into moire). */
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 o_col;
layout(set = 0, binding = 0) uniform sampler2D u_src;
layout(push_constant) uniform PC {
vec4 u_src_rect; /* displayed region in normalized src tex coords: x0,y0,x1,y1 */
vec2 u_out_size; /* viewport (letterbox rect) size in px */
vec2 u_native; /* native source resolution (w = px per line, h = scanlines) */
int u_kind;
} pc;

vec3 fetch(vec2 uv) {
uv = clamp(uv, vec2(0.0), vec2(1.0));
return texture(u_src, mix(pc.u_src_rect.xy, pc.u_src_rect.zw, uv)).rgb;
}
vec3 to_lin(vec3 c) { return pow(max(c, 0.0), vec3(2.4)); }
vec3 to_gam(vec3 c) { return pow(max(c, 0.0), vec3(1.0 / 2.2)); }

/* Horizontally pre-filtered, gamma-decoded source sample at scanline y. */
vec3 line_sample(float x, float y) {
float hx = (pc.u_kind == 2) ? 1.1 : 0.4; /* blur radius, native px */
vec2 d = vec2(hx / pc.u_native.x, 0.0);
vec2 uv = vec2(x, y);
vec3 c = fetch(uv) * 0.5 + (fetch(uv - d) + fetch(uv + d)) * 0.25;
if (pc.u_kind == 2) { /* composite chroma bleed */
c.r = mix(c.r, fetch(uv - 2.0 * d).r, 0.4);
c.b = mix(c.b, fetch(uv + 2.0 * d).b, 0.4);
}
return to_lin(c);
}

void main() {
float lines = max(pc.u_native.y, 1.0);
float ly = v_uv.y * lines; /* position in scanline space */
float lc = floor(ly - 0.5) + 0.5; /* nearest line centre */

/* Energy-conserving gaussian beam over the 3 nearest scanlines. */
vec3 beam = vec3(0.0);
const float INV_SQRT_2PI = 0.3989423;
for (int i = -1; i <= 1; i++) {
float yc = lc + float(i);
vec3 c = line_sample(v_uv.x, yc / lines);
float lum = clamp(dot(c, vec3(0.299, 0.587, 0.114)), 0.0, 1.0);
float sigma = mix(0.30, 0.55, lum); /* bright lines bloom wider */
float d = ly - yc;
beam += c * exp(-0.5 * d * d / (sigma * sigma)) * (INV_SQRT_2PI / sigma);
}

/* Too few output px per scanline -> fade the beam into a flat sample. */
float px_per_line = pc.u_out_size.y / lines;
float fade = clamp(px_per_line * 0.5 - 0.5, 0.0, 1.0);
vec3 col = mix(line_sample(v_uv.x, v_uv.y), beam, fade);

/* Phosphor mask (output-pixel space), gain-compensated in linear light. */
float mstr = (pc.u_kind == 2) ? 0.0 : 0.5;
mstr *= fade; /* tiny windows: skip mask too */
if (mstr > 0.0) {
float x = gl_FragCoord.x;
if (pc.u_kind == 1) /* shadow mask: triads, half-
* period shift every 2 rows */
x += (mod(floor(gl_FragCoord.y * 0.5), 2.0) < 1.0) ? 0.0 : 1.5;
int m = int(mod(x, 3.0));
vec3 triad = vec3(m == 0 ? 1.0 : 0.0, m == 1 ? 1.0 : 0.0, m == 2 ? 1.0 : 0.0);
col *= mix(vec3(1.0), triad, mstr);
col /= 1.0 - mstr * (2.0 / 3.0);
}

o_col = vec4(to_gam(min(col, 1.0)), 1.0);
}
9 changes: 9 additions & 0 deletions runtime/shaders/crt.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#version 450
/* Fullscreen triangle for the CRT present pass. The viewport is set to the
* letterbox rect, so v_uv [0,1] spans exactly the displayed picture. */
layout(location = 0) out vec2 v_uv;
void main() {
vec2 p = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2));
v_uv = p;
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
}
15 changes: 14 additions & 1 deletion runtime/src/dirty_ram_interp.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ extern void psx_dispatch_call(CPUState* cpu, uint32_t addr, uint32_t return_addr

/* Forward decls from memory.c — used to read instruction bytes. */
extern uint8_t *memory_get_ram_ptr(void);
extern void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len);

/* MIPS instruction field decoders. */
static inline uint32_t op_field (uint32_t i) { return (i >> 26) & 0x3Fu; }
Expand Down Expand Up @@ -2210,7 +2211,19 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_
}
#define OV_FPLOG_RET1() do { if (_ovfp) overlay_fp_log(addr, _in_regs, cpu, 0); return 1; } while (0)

if (!dirty_ram_is_dirty(phys) && !clean_game_text_miss) return 0;
if (!dirty_ram_is_dirty(phys) && !clean_game_text_miss) {
/* Some games populate post-EXE overlays through bulk host transfers that
* do not pass through the normal RAM write hooks. A real control transfer
* to decodable code above the configured boot-EXE text end is sufficient
* evidence that the page is executable; mark it so local overlay flow can
* remain in the interpreter. Invalid/data targets still fail dispatch. */
if (phys < (2u * 1024u * 1024u) && phys >= g_overlay_region_floor &&
dirty_ram_word_looks_decodable(fetch_word(phys))) {
dirty_ram_mark_executable_range(phys, 4u);
} else {
return 0;
}
}

/* Interp-pressure signal for variant-capture automation (step 2.8):
* counts dispatches the interpreter actually handles inside a capture
Expand Down
Loading