mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Merge pull request #715: race-free host API present cadence
Atomic/versioned telemetry and presenter-owned reset; explicitly measures host API completion. Focused and combined runtime/title checks passed; owner explicitly authorized this merge.
This commit is contained in:
@@ -737,6 +737,7 @@ Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this bloc
|
||||
| `pc_probe_arm` | ✓ | | |
|
||||
| `pc_probe_clear` | ✓ | | |
|
||||
| `pc_probe_dump` | ✓ | | |
|
||||
| `perf` | ✓ | | |
|
||||
| `pgxp` | ✓ | | ✓ |
|
||||
| `pgxp_miss_ring` | ✓ | | |
|
||||
| `pgxp_shadow` | ✓ | | |
|
||||
|
||||
@@ -30,6 +30,18 @@ void psx_video_telemetry_present(int result);
|
||||
* Completion/error counters remain available in that case. */
|
||||
void psx_video_telemetry_snapshot(PsxVideoTelemetry *out);
|
||||
|
||||
typedef struct PsxPresentCadence {
|
||||
int valid;
|
||||
uint32_t count, mean_us, p50_us, p95_us, p99_us, max_us, hitches;
|
||||
double span_ms, fps;
|
||||
} PsxPresentCadence;
|
||||
/* Host API completion intervals, shared by all backends; no monitor-display
|
||||
* guarantee. Bounded passive snapshot. reset is consumed by the presenter. */
|
||||
void psx_video_telemetry_cadence(int window_ms, int reset, PsxPresentCadence *out);
|
||||
int psx_video_telemetry_cadence_json(int window_ms, int reset, char *out, int capacity);
|
||||
/* Explicit monotonic host microseconds for adapters and deterministic tests. */
|
||||
void psx_video_telemetry_present_at(int result, uint64_t now_us);
|
||||
|
||||
/* Emulation-owner settings after runtime mod overrides, sampled every VBlank.
|
||||
* Actual allocated scale/dimensions come from the separate backend snapshot. */
|
||||
typedef struct PsxVideoConfigTelemetry {
|
||||
|
||||
@@ -9447,6 +9447,15 @@ static void handle_ws_aspect(int id, const char *json)
|
||||
}
|
||||
|
||||
extern int psx_debug_display_aspect(int num, int den, int adaptive);
|
||||
/* perf: host API completion cadence across the active presentation backend. */
|
||||
extern int psx_video_telemetry_cadence_json(int window_ms, int reset, char *out, int cap);
|
||||
static void handle_perf(int id, const char *json) {
|
||||
char buf[512];
|
||||
psx_video_telemetry_cadence_json(json_get_int(json, "window_ms", 0), json_get_int(json, "reset", 0),
|
||||
buf, sizeof buf);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,%s}", id, buf);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -16370,6 +16379,7 @@ static const CmdEntry s_commands[] = {
|
||||
{ "kernel_bless", handle_kernel_bless },
|
||||
{ "ws_aspect", handle_ws_aspect },
|
||||
{ "display_aspect", handle_display_aspect },
|
||||
{ "perf", handle_perf },
|
||||
{ "window_size", handle_window_size },
|
||||
{ "ws_nw", handle_ws_nw },
|
||||
{ "debug_key", handle_debug_key },
|
||||
|
||||
@@ -9263,6 +9263,15 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
|
||||
snprintf(title, sizeof(title), "%s [Game %.0f fps %.2fx]",
|
||||
s_fps_base_title.c_str(), fps, speed);
|
||||
}
|
||||
{
|
||||
PsxPresentCadence cadence{};
|
||||
psx_video_telemetry_cadence(1000, 0, &cadence);
|
||||
const double sfps = cadence.fps;
|
||||
const unsigned hitch = cadence.hitches, worst = cadence.max_us / 1000;
|
||||
size_t l = strlen(title);
|
||||
snprintf(title + l, sizeof(title) - l, " [Present %.1f fps, %u hitch, max %u ms]",
|
||||
sfps, hitch, worst);
|
||||
}
|
||||
SDL_SetWindowTitle(sdl_window, title);
|
||||
}
|
||||
if (!g_headless) {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
#include "video_telemetry.h"
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
std::atomic<uint64_t> presents{0}, errors{0};
|
||||
std::atomic<int> status_available{0};
|
||||
std::atomic<unsigned> generation{0};
|
||||
std::atomic<int> fields[8]{};
|
||||
constexpr unsigned cadence_capacity = 4096;
|
||||
std::atomic<uint32_t> intervals[cadence_capacity]{};
|
||||
std::atomic<uint64_t> cadence_version{0}, cadence_count{0};
|
||||
std::atomic<bool> cadence_reset{false};
|
||||
// Only the current present owner touches these. Readers request reset rather
|
||||
// than changing the producer's cursor or timestamp.
|
||||
uint64_t last_present_us;
|
||||
bool have_last_present;
|
||||
}
|
||||
|
||||
extern "C" void psx_video_telemetry_compose(int renderer, int scale, int sw, int sh,
|
||||
@@ -20,8 +32,75 @@ extern "C" void psx_video_telemetry_compose(int renderer, int scale, int sw, int
|
||||
}
|
||||
|
||||
extern "C" void psx_video_telemetry_present(int result) {
|
||||
const auto now = std::chrono::steady_clock::now().time_since_epoch();
|
||||
psx_video_telemetry_present_at(result,
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now).count());
|
||||
}
|
||||
|
||||
extern "C" void psx_video_telemetry_present_at(int result, uint64_t now_us) {
|
||||
status_available.store(result >= 0, std::memory_order_relaxed);
|
||||
(result != 0 ? presents : errors).fetch_add(1, std::memory_order_relaxed);
|
||||
if (result == 0) return;
|
||||
cadence_version.fetch_add(1);
|
||||
if (cadence_reset.exchange(false)) {
|
||||
cadence_count.store(0);
|
||||
have_last_present = false;
|
||||
}
|
||||
if (have_last_present && now_us > last_present_us) {
|
||||
const uint64_t count = cadence_count.load();
|
||||
const auto delta = std::min<uint64_t>(now_us - last_present_us, UINT32_MAX);
|
||||
intervals[count % cadence_capacity].store(static_cast<uint32_t>(delta));
|
||||
cadence_count.store(count + 1);
|
||||
}
|
||||
last_present_us = now_us;
|
||||
have_last_present = true;
|
||||
cadence_version.fetch_add(1);
|
||||
}
|
||||
|
||||
extern "C" void psx_video_telemetry_cadence(int window_ms, int reset, PsxPresentCadence *out) {
|
||||
if (!out) return;
|
||||
*out = {};
|
||||
uint32_t values[cadence_capacity]; // each reader owns its sorting storage
|
||||
uint64_t sum = 0;
|
||||
for (unsigned attempt = 0; attempt < 3; ++attempt) {
|
||||
out->count = 0; sum = 0;
|
||||
const uint64_t before = cadence_version.load();
|
||||
if (before & 1u) continue;
|
||||
if (cadence_reset.load()) { out->valid = 1; break; }
|
||||
const uint64_t count = cadence_count.load();
|
||||
const unsigned n = static_cast<unsigned>(std::min<uint64_t>(count, cadence_capacity));
|
||||
for (unsigned i = 0; i < n; ++i) {
|
||||
const uint32_t value = intervals[(count - 1 - i) % cadence_capacity].load();
|
||||
if (window_ms > 0 && sum + value > static_cast<uint64_t>(window_ms) * 1000) break;
|
||||
sum += value; values[out->count++] = value;
|
||||
}
|
||||
if (before == cadence_version.load()) { out->valid = 1; break; }
|
||||
}
|
||||
if (reset) cadence_reset.store(true);
|
||||
if (!out->valid) { *out = {}; return; }
|
||||
if (!out->count) return;
|
||||
std::sort(values, values + out->count);
|
||||
const auto k = out->count;
|
||||
out->mean_us = static_cast<uint32_t>(sum / k);
|
||||
out->p50_us = values[k / 2];
|
||||
out->p95_us = values[(k - 1) * 95 / 100];
|
||||
out->p99_us = values[(k - 1) * 99 / 100];
|
||||
out->max_us = values[k - 1];
|
||||
for (unsigned i = 0; i < k; ++i)
|
||||
if (static_cast<uint64_t>(values[i]) * 2 > static_cast<uint64_t>(out->p50_us) * 3)
|
||||
++out->hitches;
|
||||
out->span_ms = sum / 1000.0;
|
||||
out->fps = sum ? k * 1e6 / static_cast<double>(sum) : 0;
|
||||
}
|
||||
|
||||
extern "C" int psx_video_telemetry_cadence_json(int window_ms, int reset, char *out, int capacity) {
|
||||
PsxPresentCadence r{};
|
||||
psx_video_telemetry_cadence(window_ms, reset, &r);
|
||||
return std::snprintf(out, capacity,
|
||||
"\"valid\":%s,\"measurement\":\"host_api_completion\",\"swaps\":%u,\"span_ms\":%.1f,"
|
||||
"\"fps\":%.2f,\"mean_us\":%u,\"p50_us\":%u,\"p95_us\":%u,\"p99_us\":%u,\"max_us\":%u,\"hitches\":%u",
|
||||
r.valid ? "true" : "false", r.count, r.span_ms, r.fps, r.mean_us,
|
||||
r.p50_us, r.p95_us, r.p99_us, r.max_us, r.hitches);
|
||||
}
|
||||
|
||||
extern "C" void psx_video_telemetry_snapshot(PsxVideoTelemetry *out) {
|
||||
|
||||
@@ -52,6 +52,42 @@ int main() {
|
||||
CHECK(sample.valid && sample.internal_scale == 10000 && sample.presents == 10002);
|
||||
CHECK(sample.present_status_available);
|
||||
CHECK(sample.present_errors == 1);
|
||||
PsxPresentCadence cadence{};
|
||||
psx_video_telemetry_cadence(0,1,&cadence);
|
||||
psx_video_telemetry_present_at(1,1000);
|
||||
psx_video_telemetry_present_at(1,11000);
|
||||
psx_video_telemetry_present_at(-1,21000);
|
||||
psx_video_telemetry_present_at(1,51000);
|
||||
psx_video_telemetry_cadence(0,0,&cadence);
|
||||
CHECK(cadence.valid && cadence.count==3 && cadence.mean_us==16666);
|
||||
CHECK(cadence.p50_us==10000 && cadence.max_us==30000 && cadence.hitches==1);
|
||||
psx_video_telemetry_cadence(35,0,&cadence);
|
||||
CHECK(cadence.valid && cadence.count==1 && cadence.max_us==30000);
|
||||
psx_video_telemetry_cadence(0,1,&cadence);
|
||||
psx_video_telemetry_cadence(0,0,&cadence);
|
||||
CHECK(cadence.valid && !cadence.count);
|
||||
psx_video_telemetry_present_at(1,100000);
|
||||
psx_video_telemetry_cadence(0,0,&cadence);
|
||||
CHECK(cadence.valid && !cadence.count); // producer reset re-anchors timing
|
||||
std::atomic<bool> cadence_done{false};
|
||||
std::atomic<bool> cadence_consistent{true};
|
||||
std::thread presenter([&] {
|
||||
for(uint64_t i=1;i<=20000;++i) psx_video_telemetry_present_at(1,100000+i*10000);
|
||||
cadence_done=true;
|
||||
});
|
||||
auto reader=[&](bool resets) {
|
||||
unsigned reads=0;
|
||||
do {
|
||||
PsxPresentCadence c{};
|
||||
psx_video_telemetry_cadence(0,resets && (++reads%17==0),&c);
|
||||
if(c.valid && c.count && (c.mean_us!=10000 || c.max_us!=10000 || c.hitches!=0))
|
||||
cadence_consistent=false;
|
||||
}while(!cadence_done.load());
|
||||
};
|
||||
std::thread other_reader([&]{reader(true);});
|
||||
reader(false);
|
||||
presenter.join();other_reader.join();
|
||||
CHECK(cadence_consistent.load());
|
||||
psx_video_telemetry_snapshot(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user