Merge release/native-rendering-20261003 into master

Keeps both lines: master's PGXP session, draw-distance clamps, masked rejects
and saturated-winding NCLIP; the release's render-pass kit additions, verified
media/disc extents, timeline/present rings, cull-scale sites. Overlay ABI v30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-04 19:27:02 -07:00
487 changed files with 135860 additions and 3332 deletions
+101 -2
View File
@@ -65,6 +65,10 @@ namespace PSXRecompV4 {
namespace {
struct RuntimeMods {
struct DiscExtent { uint32_t lba, count; const uint8_t* data; };
std::vector<DiscExtent> disc_extents;
uint32_t extent_start = 0;
bool activating = false;
ModPackageManager manager;
ModResolution plan;
ModResolution validation;
@@ -1223,6 +1227,8 @@ bool mod_runtime_initialize(const std::filesystem::path& root,
RuntimeMods& s = state();
s.manager.set_root({});
clear_function_entry_hooks();
s.disc_extents.clear();
s.extent_start = 0;
s.plan = {};
s.validation = {};
s.raw_disc_index.clear();
@@ -1277,6 +1283,8 @@ bool mod_runtime_clear_for_netplay(std::string* error) {
return true;
}
clear_function_entry_hooks();
s.disc_extents.clear();
s.extent_start = 0;
s.plan = {};
s.validation = {};
s.raw_disc_index.clear();
@@ -1350,6 +1358,8 @@ bool mod_runtime_commit(const std::filesystem::path& disc_path, std::string* err
/* Hooks follow activation, never a bare commit: a new plan runs none of
* its function-entry hooks until mod_runtime_activate_plugins(). */
clear_function_entry_hooks();
s.disc_extents.clear();
s.extent_start = 0;
s.plan = std::move(plan);
build_disc_index(s);
s.effective_disc_path = std::move(effective_disc);
@@ -1447,6 +1457,67 @@ extern "C" void mod_runtime_enable_disc_patches(void) {
PSXRecompV4::state().disc_enabled = true;
}
extern "C" int psx_mod_append_disc_extent(const char* resource_id,
uint64_t byte_offset, uint32_t sector_count, uint32_t* first_lba) {
using namespace PSXRecompV4;
if (first_lba) *first_lba = 0;
auto& s = state();
const uint8_t* data = nullptr;
uint64_t size = 0;
if (!s.activating || !first_lba || !sector_count ||
!psx_mod_current_resource_bytes(resource_id, &data, &size) ||
byte_offset > size || uint64_t(sector_count)*2336 > size-byte_offset) return 0;
try {
uint32_t start = s.extent_start;
if (s.disc_extents.empty()) {
PS1::ISOReader reader;
const auto& mount = s.effective_disc_path.empty() ? s.disc_path : s.effective_disc_path;
if (mount.empty() || !reader.Open(mount.string()) || reader.TrackCount() >= 99) return 0;
start = reader.GetSectorCount();
if (!start) return 0;
}
const uint32_t lba = s.disc_extents.empty() ? start :
s.disc_extents.back().lba + s.disc_extents.back().count;
if (uint64_t(lba)+sector_count+150 > 450000) return 0;
// Mode-2's duplicated subheader is part of the format contract.
for (uint32_t i=0; i<sector_count; ++i) {
const auto* sector = data+byte_offset+uint64_t(i)*2336;
if (std::memcmp(sector,sector+4,4)) return 0;
}
s.disc_extents.push_back({lba,sector_count,data+byte_offset});
s.extent_start = start;
*first_lba = lba;
return 1;
} catch (...) { return 0; }
}
extern "C" uint32_t mod_runtime_disc_extent_start(void) {
const auto& s=PSXRecompV4::state();
return s.disc_enabled && !s.disc_extents.empty() ? s.extent_start : 0;
}
extern "C" uint32_t mod_runtime_disc_sector_count(uint32_t base_count) {
const auto& s=PSXRecompV4::state();
return mod_runtime_disc_extent_start() ?
std::max(base_count,s.disc_extents.back().lba+s.disc_extents.back().count) : base_count;
}
extern "C" int mod_runtime_read_disc_extent(uint32_t lba, int raw,
uint8_t* bytes, uint32_t size) {
if (!bytes || size < (raw ? 2352u : 2048u) || !mod_runtime_disc_extent_start()) return 0;
const auto& extents=PSXRecompV4::state().disc_extents;
for (const auto& e:extents) if (lba>=e.lba && lba-e.lba<e.count) {
const auto* sector=e.data+uint64_t(lba-e.lba)*2336;
if (raw) {
std::memset(bytes,0,16);std::memset(bytes+1,0xff,10);
const uint32_t msf=lba+150;
const auto bcd=[](uint32_t v) {return uint8_t((v/10)*16+v%10);};
bytes[12]=bcd(msf/4500);bytes[13]=bcd(msf/75%60);bytes[14]=bcd(msf%75);bytes[15]=2;
std::memcpy(bytes+16,sector,2336);
} else std::memcpy(bytes,sector+8,2048);
return 1;
}
return 0;
}
extern "C" int psx_mod_read_disc_file(const char* path, void* buffer,
uint32_t capacity, uint32_t* size) {
using namespace PSXRecompV4;
@@ -1491,10 +1562,14 @@ extern "C" void mod_runtime_activate_plugins(void) {
gpu_ws_set_native_scene_predicate(nullptr);
psx_ram_reset_size_request();
if (!s.initialized || !s.plan.ok) return;
s.disc_extents.clear();
s.extent_start = 0;
s.activating = true;
for (const ModResolution::Plugin& plugin : s.plan.plugins) {
PluginCallbackScope scope(s, &plugin);
mod_invoke_activation_plugin(plugin.id);
}
s.activating = false;
build_function_entry_hooks(s);
}
@@ -1562,6 +1637,27 @@ extern "C" uint8_t psx_mod_read_byte(uint32_t address) {
return psx_read_byte(address);
}
extern "C" int psx_mod_current_resource_bytes(const char* resource_id,
const uint8_t** bytes,
uint64_t* size) {
using namespace PSXRecompV4;
if (bytes) *bytes = nullptr;
if (size) *size = 0;
if (!resource_id || !*resource_id || !bytes || !size) return 0;
const auto& s = state();
if (!s.initialized || !s.plan.ok || !s.current_plugin) return 0;
for (const auto& resource : s.plan.resources) {
if (resource.package_id == s.current_plugin->package_id &&
resource.feature_id == s.current_plugin->feature_id &&
resource.id == resource_id && resource.bytes) {
*bytes = resource.bytes->data();
*size = resource.bytes->size();
return 1;
}
}
return 0;
}
extern "C" void psx_mod_write_byte(uint32_t address, uint8_t value) {
psx_host_write_byte(address, value);
}
@@ -1769,9 +1865,12 @@ extern "C" int psx_mod_function_entry(CPUState* cpu, uint32_t address) {
PluginCallbackScope scope(s, it->plugin, cpu);
++function_entry_depth;
if (it->callback) it->callback(cpu, address);
const bool finished = s.current_function_finished || (it->filter && it->filter(cpu, address));
/* Either completion form skips the body: an entry callback that
* called psx_mod_finish_function(), or a filter returning nonzero. */
const bool handled = s.current_function_finished ||
(it->filter && it->filter(cpu, address));
--function_entry_depth;
if (finished) {
if (handled) {
cpu->pc = cpu->gpr[31];
return 1;
}