mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Add opt-in retained texture banks and enhancement memory snapshots
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# Retained-terrain services (experimental, opt-in)
|
||||
|
||||
Issue: `beads-eio.3.156`. These services do not replace the faithful renderer.
|
||||
They support a trusted, statically linked game plugin retaining original assets
|
||||
outside guest RAM/VRAM. The first consumer is Crash's N. Sanity Beach experiment.
|
||||
|
||||
- `psx_mod_read_disc_file`: emulation-thread-only, reads the original or derived
|
||||
mounted ISO/CUE/BIN/CHD through an independent reader. Query with NULL/0, then
|
||||
supply capacity for the whole file (64 MiB ceiling). Applies active raw/user
|
||||
sector patches; never seeks the emulated CD controller. Current ISO path
|
||||
lookup supports root files and one subdirectory.
|
||||
- Immutable texture banks: stable nonzero 16-bit IDs, owned copies of PS1
|
||||
texels/indices and palettes, lazy OpenGL upload. ID 0 means ordinary VRAM.
|
||||
256 MiB aggregate host budget, maximum 1024x512 words per bank. A resolver can
|
||||
rebuild a missing bank after a cold save-state restore. IDs must not be reused
|
||||
for different assets during a process lifetime. Software/Vulkan and the GL
|
||||
CPU-authoritative dual path report unsupported.
|
||||
- Trusted GT3 packet arenas: bank ID in unused C1/C2 colour high bytes; optional
|
||||
fractional XY/reciprocal-depth suffix. Only explicitly allocated packet
|
||||
arenas interpret this extension; stock packets retain their meaning. See
|
||||
`runtime/include/mod_plugins.h` for the wire layout. CPU-side immutable assets
|
||||
are intentionally not serialized. No host/GL pointers are written to guest
|
||||
memory or save states.
|
||||
- GPU-DMA enhancement aperture: allocated-only physical 0x00800000..0x00FFFFFF
|
||||
(8 MiB), outside all four PS1 RAM mirrors. Unallocated DMA addresses retain
|
||||
retail 2 MiB folding. BIOS/expansion/MMIO aliases cannot map to this aperture.
|
||||
- Save states with allocations use v8 and a required enhancement-memory section.
|
||||
Both CPU and DMA allocations are captured; a layout cookie rejects older or
|
||||
differently sized layouts before applying RAM. Vanilla saves still write v7.
|
||||
Allocate during activation, not while rewind is recording. A matching layout
|
||||
is necessary, not a substitute for the game's asset/version compatibility.
|
||||
|
||||
The texture bank store lives for the process lifetime. This prototype is not a
|
||||
general eviction/reload API, and there is no global terrain depth buffer or bulk
|
||||
native mesh submission API yet. The Crash prototype's textured whole-level path
|
||||
is substantially slower than its stock path; do not treat it as production-ready.
|
||||
|
||||
## Validation
|
||||
|
||||
Source-owned CTests: `mod_texture_banks_test`, `mod_memory_snapshot_test`,
|
||||
`mod_gpu_dma_aperture_test`, `mod_runtime_test`, `gl_readback_runner_test` pass.
|
||||
The real-GL readback fixture passes 82 checks at both 1x and 4x on NVIDIA,
|
||||
including retained 4-bit CLUT/16-bit textures, VRAM bank transitions and painter
|
||||
order. `run_gl_readback_region.py --sdl-library` accepts an explicit system SDL3
|
||||
import library in addition to its default static dependency.
|
||||
|
||||
Crash's expanded-memory save/load and cold asset reconstruction were exercised
|
||||
live. Baseline rewind was previously validated; this new enhanced path still
|
||||
needs a dedicated live rewind acceptance run and performance profiling.
|
||||
|
||||
## TCP-only view diagnostics
|
||||
|
||||
`display_aspect num=21 den=9` changes the rendered view without moving, resizing
|
||||
or focusing the window. `display_aspect num=3 den=1 adaptive=1` resumes adaptive
|
||||
view, capped at 3:1. Neither writes settings. Unlike `ws_aspect` (GTE-only), this
|
||||
updates the presented view and native-wide surface. Use `screenshot` for the
|
||||
rendered native-wide buffer or `present_shot` for the composed window surface.
|
||||
@@ -23,6 +23,16 @@ psxrecomp_add_runtime_target(psx-runtime
|
||||
# (docs/ENHANCEMENTS.md G1.10). Titles get their <exe>_pgxp the same way.
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(mod_texture_banks_test tests/test_mod_texture_banks.c src/mod_texture_banks.c)
|
||||
target_include_directories(mod_texture_banks_test PRIVATE include)
|
||||
add_test(NAME mod_texture_banks_test COMMAND mod_texture_banks_test)
|
||||
if(NOT MSVC)
|
||||
add_executable(mod_memory_snapshot_test tests/test_mod_memory_snapshot.c)
|
||||
target_include_directories(mod_memory_snapshot_test PRIVATE include src)
|
||||
target_compile_options(mod_memory_snapshot_test PRIVATE -O2 -flto -ffunction-sections -fdata-sections)
|
||||
target_link_options(mod_memory_snapshot_test PRIVATE -flto -Wl,--gc-sections)
|
||||
add_test(NAME mod_memory_snapshot_test COMMAND mod_memory_snapshot_test)
|
||||
endif()
|
||||
# Optional real-hardware regression. Configure its dependencies explicitly;
|
||||
# absence must remain visible, never counted as a passing GPU test.
|
||||
set(PSX_GL_READBACK_SDL_ROOT "" CACHE PATH "SDL3 static build root for GL readback test")
|
||||
|
||||
@@ -42,7 +42,9 @@ extern "C" {
|
||||
* v5 = v4 + CD-ROM Sub-Q replacement state;
|
||||
* v6 = v5 + per-word GPU DMA2 linked-list progress;
|
||||
* v7 = v6 + pending XA DATA_END IRQ state. */
|
||||
#define BOOT_STATE_VERSION 7u
|
||||
#define BOOT_STATE_VERSION 8u
|
||||
/* v8 adds allocated enhancement memory. Vanilla writers remain v7; older
|
||||
* runtimes must not accept enhanced snapshots while ignoring their arenas. */
|
||||
/* v7 intentionally breaks older savestates after the CD-ROM wire grew. Reject
|
||||
* them at the header before any section changes the live machine. */
|
||||
#define BOOT_STATE_VERSION_MIN_READ 7u
|
||||
@@ -65,7 +67,7 @@ typedef struct {
|
||||
uint32_t codegen_ver; /* PSX_OVERLAY_CODEGEN_VER */
|
||||
/* ---- layout ---- */
|
||||
uint32_t section_count; /* number of sections that follow */
|
||||
uint32_t reserved; /* 0 */
|
||||
uint32_t reserved; /* v8: enhancement-memory layout cookie; vanilla 0 */
|
||||
} BootStateHeader;
|
||||
|
||||
#define BOOT_STATE_HEADER_WIRE_BYTES 36u
|
||||
@@ -82,6 +84,7 @@ typedef struct {
|
||||
* never allowed) -> normal boot + recapture.
|
||||
*/
|
||||
enum {
|
||||
BS_SEC_MODMEM = 0x11, /* allocated opt-in CPU/GPU enhancement arenas */
|
||||
BS_SEC_CPU = 0x01, /* CPUState: gpr/pc/hi/lo/cop0/gte_data/gte_ctrl */
|
||||
BS_SEC_RAM = 0x02, /* 2 MB main RAM */
|
||||
BS_SEC_SPAD = 0x03, /* 1 KB scratchpad */
|
||||
|
||||
@@ -18,6 +18,10 @@ extern "C" {
|
||||
/* Create the GL context on a window made with SDL_WINDOW_OPENGL.
|
||||
* Returns 1 on success, 0 to fall back to the SDL_Renderer present path. */
|
||||
int gl_renderer_init_context(struct SDL_Window *win);
|
||||
/* Select a retained immutable bank for the next textured submission; zero
|
||||
* selects live VRAM. Emulation/GL owning thread only. Returns 0 if unavailable. */
|
||||
int gl_renderer_select_texture_bank(uint16_t id);
|
||||
int gl_renderer_texture_banks_supported(void);
|
||||
|
||||
/* Set the GL swap interval / vsync mode (1=vsync, 0=immediate, -1=adaptive).
|
||||
* Safe before or after context creation; applies live when a context exists. */
|
||||
|
||||
@@ -9,18 +9,18 @@ extern "C" {
|
||||
|
||||
/*
|
||||
* GPU linked-list tags carry only 24 address bits. Trusted enhancements that
|
||||
* need a larger primitive arena use this otherwise-unmapped 4 MiB aperture so
|
||||
* need a larger primitive arena use this otherwise-unmapped 8 MiB aperture so
|
||||
* their CPU pointers survive the tag truncation. Nothing is mapped until an
|
||||
* enhancement explicitly allocates from it.
|
||||
*/
|
||||
#define PSX_MOD_GPU_DMA_APERTURE_BASE 0x00C00000u
|
||||
#define PSX_MOD_GPU_DMA_APERTURE_SIZE (4u * 1024u * 1024u)
|
||||
#define PSX_MOD_GPU_DMA_GUEST_BASE 0x80C00000u
|
||||
#define PSX_MOD_GPU_DMA_APERTURE_BASE 0x00800000u
|
||||
#define PSX_MOD_GPU_DMA_APERTURE_SIZE (8u * 1024u * 1024u)
|
||||
#define PSX_MOD_GPU_DMA_GUEST_BASE 0x80800000u
|
||||
|
||||
static inline int psx_mod_gpu_dma_aperture_offset_for(
|
||||
uint32_t address, uint32_t width, uint32_t used, uint32_t *offset) {
|
||||
/* CPU addresses retain all physical bits after KSEG normalization. The
|
||||
* BIOS at 0x1FC00000 must never alias the aperture at 0x00C00000.
|
||||
* BIOS at 0x1FC00000 must never alias the enhancement aperture.
|
||||
* DMA callers pass an already-truncated 24-bit tag separately below. */
|
||||
uint32_t canonical = address & 0x1FFFFFFFu;
|
||||
uint32_t off;
|
||||
@@ -48,6 +48,13 @@ static inline uint32_t psx_mod_gpu_dma_resolve_address_for(
|
||||
|
||||
uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t size, uint32_t alignment);
|
||||
uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address);
|
||||
uint32_t psx_mod_memory_alloc(uint32_t size, uint32_t alignment);
|
||||
/* Allocations are fixed during activation. Snapshot allocated bytes, never
|
||||
* host pointers; immutable host assets are reconstructed separately. */
|
||||
uint32_t psx_mod_memory_snapshot_bytes(void);
|
||||
void psx_mod_memory_snapshot_write(uint8_t* out);
|
||||
int psx_mod_memory_snapshot_read(const uint8_t* data, uint32_t size);
|
||||
uint32_t psx_mod_memory_layout_cookie(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -28,6 +28,30 @@ void psx_mod_function_entry(struct CPUState* cpu, uint32_t address);
|
||||
|
||||
/* Narrow guest services available to trusted plugin callbacks. */
|
||||
int psx_mod_game_started(void);
|
||||
/* Read an original mounted-disc file without changing guest CD state/timing.
|
||||
* Emulation-thread callbacks only. NULL buffer + zero capacity queries size;
|
||||
* otherwise capacity must hold the entire file. Active sector mods apply. */
|
||||
int psx_mod_read_disc_file(const char* path, void* buffer, uint32_t capacity,
|
||||
uint32_t* size);
|
||||
/* Experimental retained-texture service (currently OpenGL only). IDs are
|
||||
* nonzero, stable game-owned identities, NOT GL names. Banks are immutable
|
||||
* 16-bit PS1 texels/indices with a caller-selected row pitch (width).
|
||||
* A missing bank may be reconstructed from original assets by the resolver,
|
||||
* including when a restored DMA queue refers to a previously unseen level. */
|
||||
typedef int (*PSXModTextureBankResolver)(uint16_t id);
|
||||
int psx_mod_texture_banks_supported(void);
|
||||
int psx_mod_define_texture_bank(uint16_t id, uint32_t width, uint32_t height,
|
||||
const uint16_t* pixels);
|
||||
void psx_mod_set_texture_bank_resolver(PSXModTextureBankResolver resolver);
|
||||
/* A dedicated GPU-DMA packet arena. Only GT3 commands sourced from this
|
||||
* allocation interpret C1/C2's otherwise-unused high bytes as a bank ID:
|
||||
* id = (C1 >> 24) | ((C2 >> 24) << 8). ID zero uses ordinary VRAM. The rest
|
||||
* of the packet is standard GP0, retaining OT order, palettes and STP blend.
|
||||
* Optional 40-byte suffix after the 40-byte tagged GT3: u32 magic 0x48545031,
|
||||
* three IEEE float 1/z weights, six IEEE float x/y coordinates. This enables
|
||||
* precise perspective rendering without transient host-pointer side tables.
|
||||
* Allocate during activation; do not mix stock game packets into this arena. */
|
||||
uint32_t psx_mod_alloc_texture_packet_memory(uint32_t size, uint32_t alignment);
|
||||
uint8_t psx_mod_read_byte(uint32_t address);
|
||||
void psx_mod_write_byte(uint32_t address, uint8_t value);
|
||||
uint16_t psx_mod_read_half(uint32_t address);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
const uint16_t* mod_texture_bank_pixels(uint16_t id, uint32_t* width, uint32_t* height);
|
||||
uint16_t mod_texture_packet_bank(uint32_t source, const uint32_t* words, uint32_t count);
|
||||
int mod_texture_packet_precision(uint32_t source, float q[3], float xy[6]);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -416,6 +416,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/mod_builtin_bezel.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/mod_packages.cpp
|
||||
${PSXRECOMP_ROOT}/runtime/src/mod_runtime.cpp
|
||||
${PSXRECOMP_ROOT}/runtime/src/mod_texture_banks.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_keybinds.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_bios_backend.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_netplay.c
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "boot_state.h"
|
||||
#include "mod_memory.h"
|
||||
#include "overlay_api.h" /* PSX_OVERLAY_CODEGEN_HASH / _ABI_TAG / _CODEGEN_VER */
|
||||
#include "dirty_ram_interp.h"
|
||||
#include "gpu.h" /* gpu_get_vram — CPU-auth mirror under dual-raster */
|
||||
@@ -364,19 +365,23 @@ static int boot_state_save_to(BsOut* o, const CPUState* cpu,
|
||||
int ok;
|
||||
memset(&h, 0, sizeof h);
|
||||
h.magic = BOOT_STATE_MAGIC;
|
||||
h.version = BOOT_STATE_VERSION;
|
||||
h.version = psx_mod_memory_snapshot_bytes() ? BOOT_STATE_VERSION : 7u;
|
||||
h.reserved = psx_mod_memory_layout_cookie();
|
||||
h.bios_checksum = bios_checksum;
|
||||
h.entry_pc = entry_pc;
|
||||
h.codegen_hash = (uint32_t)PSX_OVERLAY_CODEGEN_HASH;
|
||||
h.abi_tag = (int32_t)PSX_OVERLAY_ABI_TAG;
|
||||
h.codegen_ver = (uint32_t)PSX_OVERLAY_CODEGEN_VER;
|
||||
h.section_count = 16;
|
||||
h.section_count = 16 + (psx_mod_memory_snapshot_bytes() ? 1u : 0u);
|
||||
|
||||
ok = write_header_le(o, &h);
|
||||
|
||||
if (ok) ok = write_cpu_section(o, cpu);
|
||||
if (ok) ok = write_section(o, BS_SEC_RAM, memory_get_ram_ptr(), RAM_SIZE);
|
||||
if (ok) ok = write_section(o, BS_SEC_SPAD, memory_get_scratchpad_ptr(), SPAD_SIZE);
|
||||
if (ok && psx_mod_memory_snapshot_bytes())
|
||||
ok = write_module_section(o, BS_SEC_MODMEM, psx_mod_memory_snapshot_bytes,
|
||||
psx_mod_memory_snapshot_write);
|
||||
if (ok) {
|
||||
/* 12B: i_stat, i_mask, cycles_since_vblank. Zeroing csv on warm load
|
||||
* rebased every tip to phase 0 and forked MotK wait-loop resim
|
||||
@@ -668,6 +673,8 @@ static int apply_section(uint32_t tag, const uint8_t* p, uint32_t len,
|
||||
free(words);
|
||||
return 1;
|
||||
}
|
||||
case BS_SEC_MODMEM:
|
||||
return psx_mod_memory_snapshot_read(p, len);
|
||||
case BS_SEC_ICACHE: {
|
||||
PstR r;
|
||||
if (len != 1024u * 4u) return 0;
|
||||
@@ -752,6 +759,10 @@ int boot_state_check_buffer(const uint8_t* file, size_t file_len,
|
||||
(unsigned)h.magic, (unsigned)BOOT_STATE_MAGIC);
|
||||
boot_state_append_reason(reason, reason_cap, part);
|
||||
}
|
||||
if (h.reserved != psx_mod_memory_layout_cookie()) {
|
||||
boot_state_append_reason(reason, reason_cap, "enhancement_memory_layout");
|
||||
return 0;
|
||||
}
|
||||
if (h.version < BOOT_STATE_VERSION_MIN_READ ||
|
||||
h.version > BOOT_STATE_VERSION) {
|
||||
snprintf(part, sizeof(part), "version=%u(want %u..%u)",
|
||||
@@ -802,7 +813,8 @@ int boot_state_load_buffer(const uint8_t* file, size_t file_len,
|
||||
(1u<<BS_SEC_CPU)|(1u<<BS_SEC_RAM)|(1u<<BS_SEC_SPAD)|(1u<<BS_SEC_IRQ)|
|
||||
(1u<<BS_SEC_TIMER)|(1u<<BS_SEC_CLOCK)|(1u<<BS_SEC_GPU)|(1u<<BS_SEC_VRAM)|
|
||||
(1u<<BS_SEC_SPU)|(1u<<BS_SEC_SPURAM)|(1u<<BS_SEC_CDROM)|(1u<<BS_SEC_DMA)|
|
||||
(1u<<BS_SEC_SIO)|(1u<<BS_SEC_MDEC)|(1u<<BS_SEC_DIRTY);
|
||||
(1u<<BS_SEC_SIO)|(1u<<BS_SEC_MDEC)|(1u<<BS_SEC_DIRTY)|
|
||||
(psx_mod_memory_snapshot_bytes() ? (1u<<BS_SEC_MODMEM) : 0u);
|
||||
uint32_t seen = 0;
|
||||
int ok = 1;
|
||||
const double t0 = boot_state_mono_ms();
|
||||
|
||||
@@ -7892,6 +7892,16 @@ static void handle_ws_aspect(int id, const char *json)
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"num\":%d,\"den\":%d}", id, num, den);
|
||||
}
|
||||
|
||||
extern int psx_debug_display_aspect(int num, int den, int adaptive);
|
||||
static void handle_display_aspect(int id, const char *json) {
|
||||
int num=json_get_int(json,"num",-1), den=json_get_int(json,"den",-1);
|
||||
int adaptive=json_get_int(json,"adaptive",0);
|
||||
if (!psx_debug_display_aspect(num,den,adaptive)) {
|
||||
send_err(id,"invalid display aspect (4:3 through 32:9)");return;
|
||||
}
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"num\":%d,\"den\":%d,\"adaptive\":%d}",id,num,den,adaptive!=0);
|
||||
}
|
||||
|
||||
/* Live native-wide vs squash toggle (A/B): ws_nw on=<0|1> re-engages the wide
|
||||
* path in the chosen mode without a relaunch. 2 = native-wide, 1 = squash. */
|
||||
extern void psx_ws_set_native_wide(int on);
|
||||
@@ -13609,6 +13619,7 @@ static const CmdEntry s_commands[] = {
|
||||
{ "ws_hud_mode", handle_ws_hud_mode },
|
||||
{ "kernel_bless", handle_kernel_bless },
|
||||
{ "ws_aspect", handle_ws_aspect },
|
||||
{ "display_aspect", handle_display_aspect },
|
||||
{ "ws_nw", handle_ws_nw },
|
||||
{ "scanline", handle_scanline },
|
||||
{ "ws_backdrop_ring", handle_ws_backdrop_ring },
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
*/
|
||||
|
||||
#include "gpu.h"
|
||||
#include "gpu_gl_renderer.h"
|
||||
#include "mod_texture_banks.h"
|
||||
#include "display_scanout.h"
|
||||
#include "pgxp.h"
|
||||
#include "mod_memory.h"
|
||||
@@ -4122,14 +4124,36 @@ static void gp0_exec_shaded_textured_tri(void) {
|
||||
}
|
||||
if (draw_area_out_bbox(vx, vy, 3)) return;
|
||||
|
||||
uint16_t host_bank = mod_texture_packet_bank(gp0_cmd_source_addr, gp0_cmd_buf, 9u);
|
||||
if (host_bank && (gr_backend() != GR_BACKEND_OPENGL ||
|
||||
!gl_renderer_select_texture_bank(host_bank))) return;
|
||||
|
||||
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
|
||||
prepare_precise_triangle(1, 4, 7,
|
||||
vx, vy);
|
||||
prepare_texture_triangle(1, 4, 7);
|
||||
if (host_bank) {
|
||||
float q[3], xy[6];
|
||||
if (mod_texture_packet_precision(gp0_cmd_source_addr, q, xy)) {
|
||||
gr_set_precise_triangle(1,
|
||||
(int32_t)((xy[0] + draw_offset_x) * 65536.0f),
|
||||
(int32_t)((xy[1] + draw_offset_y) * 65536.0f),
|
||||
(int32_t)((xy[2] + draw_offset_x) * 65536.0f),
|
||||
(int32_t)((xy[3] + draw_offset_y) * 65536.0f),
|
||||
(int32_t)((xy[4] + draw_offset_x) * 65536.0f),
|
||||
(int32_t)((xy[5] + draw_offset_y) * 65536.0f));
|
||||
gr_set_perspective_triangle(1, q[0], q[1], q[2]);
|
||||
}
|
||||
}
|
||||
gr_draw_shaded_textured_triangle(vx[0], vy[0], u[0], v[0], c[0],
|
||||
vx[1], vy[1], u[1], v[1], c[1],
|
||||
vx[2], vy[2], u[2], v[2], c[2],
|
||||
clut_x, clut_y, tpage, raw_texture);
|
||||
if (host_bank) (void)gl_renderer_select_texture_bank(0);
|
||||
}
|
||||
|
||||
int psx_mod_texture_banks_supported(void) {
|
||||
return gr_backend() == GR_BACKEND_OPENGL && gl_renderer_texture_banks_supported();
|
||||
}
|
||||
|
||||
/* Execute shaded textured quad (GP0 0x3C-0x3F) */
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
#include "gpu_render.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include "gpu_gl_renderer.h"
|
||||
#include "mod_texture_banks.h"
|
||||
#include "frame_interpolation.h"
|
||||
#include "host_osd.h"
|
||||
#include "psx_savestate_menu.h"
|
||||
@@ -359,6 +360,8 @@ static void interp_draw_quad(float alpha, int lx, int ly, int lw, int lh);
|
||||
static void present_bezel(int ww, int wh, int lx, int ly, int lw, int lh);
|
||||
|
||||
static int s_raster_ok = 0; /* full GPU pipeline available */
|
||||
static GLuint s_bank_tex[65536];
|
||||
static GLuint s_selected_bank_tex;
|
||||
|
||||
/* Authoritative VRAM: hr color texture + stencil (mask bit) FBO. */
|
||||
static GLuint s_hr_tex = 0, s_hr_fbo = 0, s_hr_rb = 0;
|
||||
@@ -1107,7 +1110,9 @@ static const char *TEX_FS =
|
||||
"uniform int u_filter; /* 1 = bilinear */\n"
|
||||
"uniform float u_shift;\n"
|
||||
"int vram_at(int x, int y){\n"
|
||||
" return int(texelFetch(u_vram, ivec2(x & 1023, y & 511), 0).r);\n"
|
||||
" ivec2 p = ivec2(x & 1023, y & 511);\n"
|
||||
" if (any(greaterThanEqual(p, textureSize(u_vram, 0)))) return 0;\n"
|
||||
" return int(texelFetch(u_vram, p, 0).r);\n"
|
||||
"}\n"
|
||||
"int fetch_texel(int u, int v){\n"
|
||||
" u &= 255; v &= 255;\n"
|
||||
@@ -1746,6 +1751,7 @@ static float s_tb[TEXBATCH_MAXV * TEXV];
|
||||
static int s_tb_n = 0; /* verts queued */
|
||||
static int s_tb_semi = -2;
|
||||
static int s_tb_mask = 0, s_tb_filter = 0;
|
||||
static GLuint s_tb_bank_tex;
|
||||
static int s_tb_twin[4] = {0, 0, 0, 0};
|
||||
static uint64_t s_batch_total = 0, s_batch_reason[7];
|
||||
|
||||
@@ -1857,7 +1863,7 @@ static void flush_tex_batch(void) {
|
||||
hr_begin(1);
|
||||
p_glUseProgram(s_tex_prog);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, s_raw_tex);
|
||||
glBindTexture(GL_TEXTURE_2D, s_tb_bank_tex ? s_tb_bank_tex : s_raw_tex);
|
||||
p_glUniform1i(s_uVram, 0);
|
||||
p_glUniform4i(s_uTwin, s_tb_twin[0], s_tb_twin[1], s_tb_twin[2], s_tb_twin[3]);
|
||||
p_glUniform1i(s_uMaskset, s_tb_mask);
|
||||
@@ -2053,7 +2059,8 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
int depth = (texpage >> 7) & 3; if (depth > 2) depth = 2;
|
||||
|
||||
flush_cpu_upload(); /* if a CPU->VRAM upload is pending it flushes the batch first */
|
||||
flush_pack_if_sampling(base_x, base_y, depth, clut_x, clut_y); /* flushes batch iff it must pack */
|
||||
if (!s_selected_bank_tex)
|
||||
flush_pack_if_sampling(base_x, base_y, depth, clut_x, clut_y);
|
||||
mark_prim_dirty(xs, ys, 3, 1 /* textured */);
|
||||
|
||||
/* Append to the textured batch. Flush first if this prim's blend/mask/twin/
|
||||
@@ -2089,7 +2096,8 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
int isolate = (semi >= 0);
|
||||
int reason = -1;
|
||||
if (s_tb_n > 0) {
|
||||
if (isolate) reason = 0;
|
||||
if (s_tb_bank_tex != s_selected_bank_tex) reason = 0;
|
||||
else if (isolate) reason = 0;
|
||||
else if (batch_semi != s_tb_semi) reason = 1;
|
||||
else if (s_mask_set != s_tb_mask) reason = 2;
|
||||
else if (s_tex_filter != s_tb_filter) reason = 3;
|
||||
@@ -2104,6 +2112,7 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
if (s_tb_n + 3 > TEXBATCH_MAXV) { s_batch_reason[6]++; flush_tex_batch(); }
|
||||
if (s_tb_n == 0) { /* opening a batch: capture its keyed state */
|
||||
s_tb_semi = batch_semi; s_tb_mask = s_mask_set; s_tb_filter = s_tex_filter; s_tb_gate = gate;
|
||||
s_tb_bank_tex = s_selected_bank_tex;
|
||||
s_tb_twin[0] = twx; s_tb_twin[1] = twy; s_tb_twin[2] = tox; s_tb_twin[3] = toy;
|
||||
}
|
||||
float *vp = &s_tb[s_tb_n * TEXV];
|
||||
@@ -2984,6 +2993,41 @@ static int init_gpu_raster(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int gl_renderer_texture_banks_supported(void) { return s_raster_ok && !s_cpu_auth_dual; }
|
||||
|
||||
int gl_renderer_select_texture_bank(uint16_t id) {
|
||||
uint32_t width, height;
|
||||
const uint16_t* pixels;
|
||||
GLint alignment, row_length;
|
||||
if (!id) { s_selected_bank_tex = 0; return 1; }
|
||||
if (!gl_renderer_texture_banks_supported()) return 0;
|
||||
if (!s_bank_tex[id]) {
|
||||
pixels = mod_texture_bank_pixels(id, &width, &height);
|
||||
if (!pixels) return 0;
|
||||
glGenTextures(1, &s_bank_tex[id]);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, s_bank_tex[id]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
|
||||
glGetIntegerv(PSXGL_UNPACK_ROW_LENGTH, &row_length);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glPixelStorei(PSXGL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, PSXGL_R16UI, (GLsizei)width,
|
||||
(GLsizei)height, 0, PSXGL_RED_INTEGER, GL_UNSIGNED_SHORT, pixels);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
|
||||
glPixelStorei(PSXGL_UNPACK_ROW_LENGTH, row_length);
|
||||
if (glGetError() != GL_NO_ERROR) {
|
||||
glDeleteTextures(1, &s_bank_tex[id]); s_bank_tex[id] = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
s_selected_bank_tex = s_bank_tex[id];
|
||||
return 1;
|
||||
}
|
||||
|
||||
int gl_renderer_init_context(SDL_Window *win) {
|
||||
s_win = win;
|
||||
s_present_w = 0;
|
||||
@@ -3084,6 +3128,12 @@ void gl_renderer_set_swap_interval(int interval) {
|
||||
}
|
||||
|
||||
void gl_renderer_shutdown(void) {
|
||||
if (s_ctx) {
|
||||
for (unsigned i = 1; i < 65536u; ++i)
|
||||
if (s_bank_tex[i]) glDeleteTextures(1, &s_bank_tex[i]);
|
||||
}
|
||||
memset(s_bank_tex, 0, sizeof s_bank_tex);
|
||||
s_selected_bank_tex = s_tb_bank_tex = 0;
|
||||
if (s_ctx) {
|
||||
ensure_cpu();
|
||||
SDL_GL_DeleteContext(s_ctx); s_ctx = NULL;
|
||||
|
||||
@@ -1774,6 +1774,22 @@ extern "C" void psx_ws_set_native_wide(int on) {
|
||||
}
|
||||
extern "C" int psx_ws_get_native_wide(void) { return g_ws_native_wide; }
|
||||
|
||||
/* TCP diagnostics: change the rendered view without moving, resizing, raising
|
||||
* or focusing the user's window. Transient; never writes settings.toml. */
|
||||
extern "C" int psx_debug_display_aspect(int num, int den, int adaptive) {
|
||||
if (adaptive) return psx_mod_set_adaptive_display_aspect(num, den);
|
||||
if (!psx_mod_set_fixed_display_aspect(num, den)) return 0;
|
||||
gl_renderer_set_display_aspect(num, den);
|
||||
vk_renderer_set_display_aspect(num, den);
|
||||
if (sdl_renderer) {
|
||||
g_logical_w = 480 * num * g_video_scale / den;
|
||||
SDL_RenderSetLogicalSize(sdl_renderer, g_logical_w, 480 * g_video_scale);
|
||||
}
|
||||
g_ws_projection_mode = -1;
|
||||
refresh_widescreen_projection();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool g_gl_active = false; /* GL context live -> GL present path */
|
||||
static bool g_vk_active = false; /* Vulkan context live -> VK present path */
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "gpu.h"
|
||||
#include "mdec.h"
|
||||
#include "mod_memory.h"
|
||||
#include "pst_wire.h"
|
||||
#include "sio.h"
|
||||
#include "spu.h"
|
||||
#include "timers.h"
|
||||
@@ -43,6 +44,45 @@ static uint32_t mod_memory_used;
|
||||
static uint8_t mod_gpu_dma_memory[PSX_MOD_GPU_DMA_APERTURE_SIZE];
|
||||
static uint32_t mod_gpu_dma_memory_used;
|
||||
|
||||
uint32_t psx_mod_memory_snapshot_bytes(void) {
|
||||
return mod_memory_used || mod_gpu_dma_memory_used
|
||||
? 16u + mod_memory_used + mod_gpu_dma_memory_used : 0u;
|
||||
}
|
||||
|
||||
uint32_t psx_mod_memory_layout_cookie(void) {
|
||||
/* Compatibility guard, not a cryptographic identity. Include aperture
|
||||
* revision; old enabled-mod saves must be rejected before applying RAM. */
|
||||
if (!psx_mod_memory_snapshot_bytes()) return 0u;
|
||||
return 0x4D4F4402u ^ (mod_memory_used * 16777619u) ^ mod_gpu_dma_memory_used;
|
||||
}
|
||||
|
||||
void psx_mod_memory_snapshot_write(uint8_t* out) {
|
||||
PstW w;
|
||||
if (!out || !psx_mod_memory_snapshot_bytes()) return;
|
||||
pst_w_init(&w, out, psx_mod_memory_snapshot_bytes());
|
||||
pst_w_u32(&w, 1u);
|
||||
pst_w_u32(&w, PSX_MOD_GPU_DMA_APERTURE_BASE);
|
||||
pst_w_u32(&w, mod_memory_used);
|
||||
pst_w_u32(&w, mod_gpu_dma_memory_used);
|
||||
memcpy(out + 16u, mod_memory, mod_memory_used);
|
||||
memcpy(out + 16u + mod_memory_used, mod_gpu_dma_memory, mod_gpu_dma_memory_used);
|
||||
}
|
||||
|
||||
int psx_mod_memory_snapshot_read(const uint8_t* data, uint32_t size) {
|
||||
PstR r;
|
||||
uint32_t version, base, cpu_bytes, dma_bytes;
|
||||
if (!data || size < 16u || size != psx_mod_memory_snapshot_bytes()) return 0;
|
||||
pst_r_init(&r, data, size);
|
||||
if (!pst_r_u32(&r, &version) || !pst_r_u32(&r, &base) ||
|
||||
!pst_r_u32(&r, &cpu_bytes) || !pst_r_u32(&r, &dma_bytes) ||
|
||||
version != 1u || base != PSX_MOD_GPU_DMA_APERTURE_BASE ||
|
||||
cpu_bytes != mod_memory_used || dma_bytes != mod_gpu_dma_memory_used)
|
||||
return 0;
|
||||
memcpy(mod_memory, data + 16u, cpu_bytes);
|
||||
memcpy(mod_gpu_dma_memory, data + 16u + cpu_bytes, dma_bytes);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Trusted mods may opt into host-backed guest memory in Expansion 1. Before
|
||||
* the first allocation this entire region retains hardware open-bus behavior.
|
||||
|
||||
@@ -1294,6 +1294,43 @@ extern "C" void mod_runtime_enable_disc_patches(void) {
|
||||
PSXRecompV4::state().disc_enabled = true;
|
||||
}
|
||||
|
||||
extern "C" int psx_mod_read_disc_file(const char* path, void* buffer,
|
||||
uint32_t capacity, uint32_t* size) {
|
||||
using namespace PSXRecompV4;
|
||||
if (size) *size = 0;
|
||||
if (!path || !*path || !size || (!buffer && capacity)) return 0;
|
||||
try {
|
||||
const auto& s = state();
|
||||
const auto& mount = s.effective_disc_path.empty() ? s.disc_path : s.effective_disc_path;
|
||||
if (mount.empty()) return 0;
|
||||
PS1::ISOReader reader;
|
||||
PS1::ISOFileEntry entry;
|
||||
if (!reader.Open(mount.string()) || !reader.FindFile(path, entry) ||
|
||||
entry.is_directory || !entry.size || entry.size > 64u * 1024u * 1024u)
|
||||
return 0;
|
||||
if (!buffer) { *size = entry.size; return 1; }
|
||||
if (capacity < entry.size) return 0;
|
||||
uint8_t sector[2048], raw[2352];
|
||||
for (uint32_t offset = 0; offset < entry.size; offset += 2048u) {
|
||||
const uint32_t lba = entry.lba + offset / 2048u;
|
||||
if (reader.ReadRawSector(lba, raw)) {
|
||||
if (raw[15] != 1 && (raw[15] != 2 || (raw[18] & 0x20u))) return 0;
|
||||
mod_runtime_patch_disc_sector(lba, 1, raw, sizeof raw);
|
||||
std::memcpy(sector, raw + (raw[15] == 1 ? 16 : 24), sizeof sector);
|
||||
if (raw[15] == 1) mod_runtime_patch_disc_sector(lba, 0, sector, sizeof sector);
|
||||
} else {
|
||||
if (!reader.ReadSector(lba, sector)) return 0;
|
||||
mod_runtime_patch_disc_sector(lba, 0, sector, sizeof sector);
|
||||
}
|
||||
if (state().disc_guard_failed) return 0;
|
||||
const uint32_t count = std::min(2048u, entry.size - offset);
|
||||
std::memcpy(static_cast<uint8_t*>(buffer) + offset, sector, count);
|
||||
}
|
||||
*size = entry.size;
|
||||
return 1;
|
||||
} catch (...) { return 0; }
|
||||
}
|
||||
|
||||
extern "C" void mod_runtime_activate_plugins(void) {
|
||||
using namespace PSXRecompV4;
|
||||
RuntimeMods& s = state();
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "mod_plugins.h"
|
||||
#include "mod_memory.h"
|
||||
#include "mod_texture_banks.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
typedef struct { uint16_t* pixels; uint32_t width, height; } Bank;
|
||||
typedef struct { uint32_t begin, size; } PacketArena;
|
||||
static Bank banks[65536];
|
||||
static PacketArena arenas[16];
|
||||
static unsigned arena_count;
|
||||
static uint32_t bank_bytes;
|
||||
static PSXModTextureBankResolver resolve_bank;
|
||||
static int resolving;
|
||||
|
||||
void psx_mod_set_texture_bank_resolver(PSXModTextureBankResolver resolver) {
|
||||
resolve_bank = resolver;
|
||||
}
|
||||
|
||||
int psx_mod_define_texture_bank(uint16_t id, uint32_t w, uint32_t h,
|
||||
const uint16_t* pixels) {
|
||||
uint32_t bytes;
|
||||
Bank* bank = &banks[id];
|
||||
if (!id || !pixels || !w || !h || w > 1024u || h > 512u) return 0;
|
||||
bytes = w * h * 2u;
|
||||
if (bank->pixels)
|
||||
return bank->width == w && bank->height == h &&
|
||||
memcmp(bank->pixels, pixels, bytes) == 0;
|
||||
if (bytes > 256u * 1024u * 1024u - bank_bytes) return 0;
|
||||
bank->pixels = (uint16_t*)malloc(bytes);
|
||||
if (!bank->pixels) return 0;
|
||||
memcpy(bank->pixels, pixels, bytes);
|
||||
bank->width = w; bank->height = h;
|
||||
bank_bytes += bytes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const uint16_t* mod_texture_bank_pixels(uint16_t id, uint32_t* w, uint32_t* h) {
|
||||
if (!id || !w || !h) return NULL;
|
||||
if (!banks[id].pixels && resolve_bank && !resolving) {
|
||||
resolving = 1;
|
||||
(void)resolve_bank(id);
|
||||
resolving = 0;
|
||||
}
|
||||
*w = banks[id].width; *h = banks[id].height;
|
||||
return banks[id].pixels;
|
||||
}
|
||||
|
||||
uint32_t psx_mod_alloc_texture_packet_memory(uint32_t size, uint32_t alignment) {
|
||||
uint32_t addr;
|
||||
if (arena_count == 16u || size < 40u) return 0;
|
||||
addr = psx_mod_gpu_dma_memory_alloc(size, alignment);
|
||||
if (!addr) return 0;
|
||||
arenas[arena_count].begin = addr & 0x1FFFFFFFu;
|
||||
arenas[arena_count++].size = size;
|
||||
return addr;
|
||||
}
|
||||
|
||||
uint16_t mod_texture_packet_bank(uint32_t source, const uint32_t* words, uint32_t count) {
|
||||
unsigned i;
|
||||
uint32_t address = source & 0x1FFFFFFFu;
|
||||
if (!words || count != 9u || (words[0] >> 26) != 0xDu) return 0;
|
||||
for (i = 0; i < arena_count; ++i) {
|
||||
if (address >= arenas[i].begin && address - arenas[i].begin <= arenas[i].size - 36u)
|
||||
return (uint16_t)((words[3] >> 24) | ((words[6] >> 24) << 8));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mod_texture_packet_precision(uint32_t source, float q[3], float xy[6]) {
|
||||
unsigned i, j;
|
||||
uint32_t address = source & 0x1FFFFFFFu;
|
||||
for (i = 0; i < arena_count; ++i) {
|
||||
if (arenas[i].size < 80u || address < arenas[i].begin ||
|
||||
address - arenas[i].begin > arenas[i].size - 76u) continue;
|
||||
if (psx_mod_read_word(source + 36u) != 0x48545031u) return 0;
|
||||
for (j = 0; j < 9u; ++j) {
|
||||
uint32_t bits = psx_mod_read_word(source + 40u + j * 4u);
|
||||
float f;
|
||||
memcpy(&f, &bits, sizeof f);
|
||||
if (!isfinite(f)) return 0;
|
||||
if (j < 3u) {
|
||||
if (f <= 0.0f || f > 1.0f) return 0;
|
||||
q[j] = f;
|
||||
} else {
|
||||
if (f < -2048.0f || f > 2048.0f) return 0;
|
||||
xy[j - 3u] = f;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -26,6 +26,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--compiler-bin", required=True)
|
||||
parser.add_argument("--sdl-root", required=True)
|
||||
parser.add_argument("--sdl-library", help="Explicit SDL3 library (e.g. a system DLL import library)")
|
||||
parser.add_argument("--output", required=True)
|
||||
parser.add_argument("--gl-source")
|
||||
parser.add_argument("--fixture", type=pathlib.Path)
|
||||
@@ -39,8 +40,9 @@ def main():
|
||||
sdl = pathlib.Path(args.sdl_root).resolve()
|
||||
if not (sdl / "sdl3-src/include").is_dir():
|
||||
parser.error(f"--sdl-root must contain the sdl3-src/include directory: {sdl}")
|
||||
if not (sdl / "sdl3-build/libSDL3.a").is_file():
|
||||
parser.error(f"--sdl-root must contain sdl3-build/libSDL3.a: {sdl}")
|
||||
sdl_library = pathlib.Path(args.sdl_library).resolve() if args.sdl_library else sdl / "sdl3-build/libSDL3.a"
|
||||
if not sdl_library.is_file():
|
||||
parser.error(f"SDL3 library does not exist: {sdl_library}")
|
||||
output = pathlib.Path(args.output).resolve()
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
dest = pathlib.Path(tempfile.mkdtemp(prefix="readback-", dir=output))
|
||||
@@ -73,7 +75,7 @@ def main():
|
||||
"oleaut32", "version", "uuid", "advapi32", "setupapi", "shell32",
|
||||
"dinput8", "opengl32"]
|
||||
if run([compiler / "g++.exe", "-O2", "-flto", dest / "probe.o", dest / "sw.o",
|
||||
sdl / "sdl3-build/libSDL3.a", *["-l" + name for name in libraries],
|
||||
sdl_library, *["-l" + name for name in libraries],
|
||||
"-o", dest / "probe.exe"]).returncode:
|
||||
return 2
|
||||
for scale in (1, 4):
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/* Original source-owned GL readback-coherence regression. No retail payload. */
|
||||
#include "gpu_gl_renderer.c"
|
||||
#include "mod_texture_banks.c"
|
||||
uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t n,uint32_t a){(void)n;(void)a;return 0;}
|
||||
uint32_t psx_mod_read_word(uint32_t a){(void)a;return 0;}
|
||||
static uint16_t image[1024*512], oracle[1024*512];
|
||||
int g_psx_vram_dirty_tracking=0;
|
||||
uint64_t s_frame_count=0;
|
||||
@@ -73,6 +76,24 @@ int main(int argc,char **argv){
|
||||
check(glb_vram_read(40,40)==0,"depth24 cleared band immediate CPU read");
|
||||
check(glb_vram_read(33,33)==0x3210,"newer overlapping texture survives clear");
|
||||
verify("depth24 leave coherence without subsequent primitive");
|
||||
/* Retained banks use their own texels AND CLUT, and bank/VRAM transitions
|
||||
* must split batches without changing painter order. No retail assets. */
|
||||
static uint16_t bank[256*128];
|
||||
bank[0]=0x001f; bank[1]=0x03e0; bank[2]=0x7c00;
|
||||
bank[16]=0x1111; /* 4-bit indices, CLUT at (0,0) */
|
||||
check(psx_mod_define_texture_bank(7,256,128,bank),"define retained bank");
|
||||
check(gl_renderer_select_texture_bank(7),"select retained bank");
|
||||
glb_draw_shaded_textured_triangle(100,250,64,0,0x808080,132,250,64,0,0x808080,100,282,64,0,0x808080,0,0,0,1);
|
||||
check(gl_renderer_select_texture_bank(0),"select original VRAM");
|
||||
glb_vram_write(512,0,0x7c00);
|
||||
glb_draw_shaded_textured_triangle(116,250,0,0,0x808080,148,250,0,0,0x808080,116,282,0,0,0x808080,0,0,0x108,1);
|
||||
check(glb_vram_read(102,252)==0x03e0,"retained 4-bit CLUT independent of guest VRAM");
|
||||
check(glb_vram_read(118,252)==0x7c00,"following stock texture wins overlap");
|
||||
check(gl_renderer_select_texture_bank(7),"reselect retained bank");
|
||||
glb_draw_shaded_textured_triangle(300,250,0,0,0x808080,332,250,0,0,0x808080,300,282,0,0,0x808080,0,0,0x100,1);
|
||||
check(gl_renderer_select_texture_bank(0),"reset bank after direct texture");
|
||||
check(glb_vram_read(302,252)==0x001f,"retained 16-bit texel");
|
||||
verify("retained banks and original VRAM ordered together");
|
||||
printf("checks=%d failures=%d\n",checks,failures);
|
||||
gl_renderer_shutdown();SDL_DestroyWindow(win);SDL_Quit();return failures?1:0;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ int main(void) {
|
||||
check(psx_mod_gpu_dma_aperture_offset_for(0xA0000000u + base, 4u, size, &off)
|
||||
&& off == 0u, "KSEG1 CPU aperture alias resolves correctly");
|
||||
check(psx_mod_gpu_dma_aperture_offset_for(base + size - 4u, 4u, size, &off)
|
||||
&& off == size - 4u, "last word of full 4 MiB aperture is accessible");
|
||||
&& off == size - 4u, "last word of full enhancement aperture is accessible");
|
||||
check(!psx_mod_gpu_dma_aperture_offset_for(base - 4u, 4u, size, &off),
|
||||
"word before aperture is rejected");
|
||||
check(!psx_mod_gpu_dma_aperture_offset_for(base + size, 4u, size, &off),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Include implementation to inspect owned backing storage without pulling in
|
||||
* hardware routing. Section GC discards unrelated memory access functions. */
|
||||
#include "memory.c"
|
||||
static void check(int ok,const char* text){if(!ok){fprintf(stderr,"FAIL %s\n",text);exit(1);}}
|
||||
int main(void){
|
||||
check(psx_mod_memory_snapshot_bytes()==0 && psx_mod_memory_layout_cookie()==0,"vanilla unchanged");
|
||||
psx_mod_memory_snapshot_write(NULL);
|
||||
check(psx_mod_memory_alloc(13,16)==0x9f000000u,"CPU allocation");
|
||||
check(psx_mod_gpu_dma_memory_alloc(5u*1024u*1024u,16)==PSX_MOD_GPU_DMA_GUEST_BASE,"beyond old four MiB");
|
||||
check(psx_mod_gpu_dma_resolve_address(0xd00000u)==0x100000u,"unallocated tag retains retail folding");
|
||||
uint32_t size=psx_mod_memory_snapshot_bytes(),cookie=psx_mod_memory_layout_cookie();
|
||||
uint8_t* saved=(uint8_t*)malloc(size);check(saved!=NULL,"test buffer");
|
||||
memset(mod_memory,0x12,mod_memory_used);memset(mod_gpu_dma_memory,0x34,mod_gpu_dma_memory_used);
|
||||
psx_mod_memory_snapshot_write(saved);
|
||||
memset(mod_memory,0x56,mod_memory_used);memset(mod_gpu_dma_memory,0x78,mod_gpu_dma_memory_used);
|
||||
check(!psx_mod_memory_snapshot_read(saved,size-1),"truncation rejected");
|
||||
saved[0]=99;
|
||||
check(!psx_mod_memory_snapshot_read(saved,size) && mod_memory[0]==0x56,"bad version no mutation");
|
||||
saved[0]=1;saved[8]++;
|
||||
check(!psx_mod_memory_snapshot_read(saved,size) && mod_gpu_dma_memory[0]==0x78,"bad layout no mutation");
|
||||
saved[8]--;
|
||||
check(psx_mod_memory_snapshot_read(saved,size),"restore");
|
||||
for(uint32_t i=0;i<mod_memory_used;++i)check(mod_memory[i]==0x12,"CPU bytes restored");
|
||||
for(uint32_t i=0;i<mod_gpu_dma_memory_used;++i)check(mod_gpu_dma_memory[i]==0x34,"DMA bytes restored");
|
||||
check(psx_mod_memory_layout_cookie()==cookie,"stable layout identity");
|
||||
check(psx_mod_gpu_dma_memory_alloc(16,16)!=0 && psx_mod_memory_layout_cookie()!=cookie,"layout change detected");
|
||||
check(!psx_mod_memory_snapshot_read(saved,size),"old layout rejected");
|
||||
free(saved);puts("enhancement snapshot checks passed");return 0;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "gpu.h"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -491,6 +492,44 @@ int main() {
|
||||
"unestablished display geometry must report zero so callers skip "
|
||||
"drawing instead of guessing");
|
||||
|
||||
/* Source-owned ISO: nested file spanning two sectors, without a derived
|
||||
* disc. Host reads must choose the original mount and leave sizes honest. */
|
||||
std::vector<uint8_t> iso(24*2048);
|
||||
auto le32 = [&](size_t at,uint32_t n) { for(unsigned i=0;i<4;++i) iso[at+i]=(uint8_t)(n>>(i*8)); };
|
||||
auto record = [&](size_t at,uint32_t lba,uint32_t bytes,bool directory,const std::string& name) {
|
||||
iso[at]=(uint8_t)((33+name.size()+1)&~size_t(1));
|
||||
le32(at+2,lba); le32(at+10,bytes); iso[at+25]=directory?2:0;
|
||||
iso[at+28]=1;iso[at+31]=1;iso[at+32]=(uint8_t)name.size();
|
||||
std::copy(name.begin(),name.end(),iso.begin()+at+33);
|
||||
};
|
||||
iso[16*2048]=1;std::copy_n("CD001",5,iso.begin()+16*2048+1);iso[16*2048+6]=1;
|
||||
record(16*2048+156,20,2048,true,std::string(1,'\0'));
|
||||
record(20*2048,21,2048,true,"S0");
|
||||
record(21*2048,22,3000,false,"LEVEL.NSF;1");
|
||||
for(unsigned i=0;i<3000;++i)iso[22*2048+i]=(uint8_t)(i*7);
|
||||
const auto disc_root=root/"host-reader";
|
||||
const auto iso_path=disc_root/"original.iso";
|
||||
write_bytes(iso_path,iso);
|
||||
check(PSXRecompV4::mod_runtime_initialize(disc_root,"READER",0,{},&error),"reader initialize");
|
||||
check(PSXRecompV4::mod_runtime_commit(iso_path,&error),"reader mount original ISO");
|
||||
uint32_t bytes=0;
|
||||
check(psx_mod_read_disc_file("S0/LEVEL.NSF",nullptr,0,&bytes) && bytes==3000,"query original nested file size");
|
||||
std::vector<uint8_t> result(3000);
|
||||
check(!psx_mod_read_disc_file("S0/LEVEL.NSF",result.data(),2999,&bytes) && bytes==0,"undersized destination rejected");
|
||||
check(psx_mod_read_disc_file("S0/LEVEL.NSF",result.data(),(uint32_t)result.size(),&bytes) &&
|
||||
bytes==3000 && std::equal(result.begin(),result.end(),iso.begin()+22*2048),"complete original file bytes");
|
||||
check(!psx_mod_read_disc_file("S0/MISSING.NSF",nullptr,0,&bytes) && bytes==0,"missing file explicit");
|
||||
check(!psx_mod_read_disc_file("S0",nullptr,0,&bytes),"directory rejected");
|
||||
std::vector<uint8_t> raw_iso(24*2352);
|
||||
for(unsigned i=0;i<24;++i) {
|
||||
raw_iso[i*2352+15]=2;
|
||||
std::copy_n(iso.begin()+i*2048,2048,raw_iso.begin()+i*2352+24);
|
||||
}
|
||||
const auto raw_path=disc_root/"original.bin";
|
||||
write_bytes(raw_path,raw_iso);
|
||||
check(PSXRecompV4::mod_runtime_commit(raw_path,&error),"reader mount raw disc");
|
||||
check(psx_mod_read_disc_file("S0/LEVEL.NSF",result.data(),(uint32_t)result.size(),&bytes) &&
|
||||
std::equal(result.begin(),result.end(),iso.begin()+22*2048),"raw and ISO reads identical");
|
||||
fs::remove_all(root, ec);
|
||||
if (failures) return 1;
|
||||
std::cout << "mod runtime tests passed\n";
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "mod_plugins.h"
|
||||
#include "mod_memory.h"
|
||||
#include "mod_texture_banks.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
static uint32_t memory[256], used, reads, resolves;
|
||||
uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t n,uint32_t a) {
|
||||
(void)a; if(n>sizeof memory-used)return 0;
|
||||
uint32_t address=PSX_MOD_GPU_DMA_GUEST_BASE+used;used+=n;return address;
|
||||
}
|
||||
uint32_t psx_mod_read_word(uint32_t address) {
|
||||
uint32_t offset=(address&0x1fffffffu)-PSX_MOD_GPU_DMA_APERTURE_BASE;
|
||||
if(offset>sizeof memory-4)abort(); ++reads;return memory[offset/4];
|
||||
}
|
||||
static void check(int ok,const char* text){if(!ok){fprintf(stderr,"FAIL %s\n",text);exit(1);}}
|
||||
static int resolve(uint16_t id){uint16_t data=0x4567;++resolves;return psx_mod_define_texture_bank(id,1,1,&data);}
|
||||
static uint32_t bits(float f){uint32_t n;memcpy(&n,&f,4);return n;}
|
||||
int main(void){
|
||||
uint16_t data[4]={1,2,3,4};uint32_t w,h;
|
||||
check(!psx_mod_define_texture_bank(0,2,2,data),"bank zero reserved");
|
||||
check(!psx_mod_define_texture_bank(1,1025,2,data),"dimensions bounded");
|
||||
check(psx_mod_define_texture_bank(1,2,2,data),"define");
|
||||
check(psx_mod_define_texture_bank(1,2,2,data),"identical definition idempotent");
|
||||
data[0]=99;
|
||||
check(!psx_mod_define_texture_bank(1,2,2,data),"immutable id cannot change");
|
||||
check(mod_texture_bank_pixels(1,&w,&h)[0]==1 && w==2 && h==2,"owned deep copy");
|
||||
check(!mod_texture_bank_pixels(2,&w,&h),"missing bank explicit");
|
||||
psx_mod_set_texture_bank_resolver(resolve);
|
||||
check(mod_texture_bank_pixels(2,&w,&h)[0]==0x4567 && resolves==1,"lazy restored-bank resolver");
|
||||
check(mod_texture_bank_pixels(2,&w,&h)!=NULL && resolves==1,"resident bank no reload");
|
||||
const uint32_t arena=psx_mod_alloc_texture_packet_memory(80,16);
|
||||
uint32_t words[9]={0x34000000,0,0,0x23000000,0,0,0x01000000,0,0};
|
||||
check(!mod_texture_packet_bank(0x80001004,words,9),"stock packet cannot opt in");
|
||||
check(mod_texture_packet_bank(arena+4,words,9)==0x123,"bank id only in trusted arena");
|
||||
check(!mod_texture_packet_bank(arena+48,words,9),"whole command must fit");
|
||||
check(!mod_texture_packet_bank(arena+4,words,8),"command size checked");
|
||||
words[0]=0x30000000;
|
||||
check(!mod_texture_packet_bank(arena+4,words,9),"GT3 opcode checked");
|
||||
float q[3],xy[6];
|
||||
check(!mod_texture_packet_precision(0x80001004,q,xy) && reads==0,"stock precision does not read suffix");
|
||||
memory[10]=0x48545031;
|
||||
for(unsigned i=0;i<3;++i)memory[11+i]=bits(.01f);
|
||||
for(unsigned i=0;i<6;++i)memory[14+i]=bits(15.25f+i);
|
||||
check(mod_texture_packet_precision(arena+4,q,xy) && q[1]==.01f && xy[5]==20.25f,"fractional projection and reciprocal depth");
|
||||
check(!mod_texture_packet_precision(arena+8,q,xy),"suffix bounds");
|
||||
memory[11]=bits(NAN);
|
||||
check(!mod_texture_packet_precision(arena+4,q,xy),"NaN rejected");
|
||||
memory[11]=bits(0);
|
||||
check(!mod_texture_packet_precision(arena+4,q,xy),"zero reciprocal depth rejected");
|
||||
puts("texture bank checks passed");return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user