mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
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:
+326
-18
@@ -31,6 +31,9 @@
|
||||
#include "gpu.h"
|
||||
#include "gpu_render.h" /* gr_scale + gr_render_display_hires (screenshot_hires) */
|
||||
#include "present_ring.h"
|
||||
#include "gpu_timeline.h"
|
||||
#include "gte_nclip_stats.h"
|
||||
#include "present_image_ring.h"
|
||||
#include "load_transition_ring.h"
|
||||
#include "cdrom.h"
|
||||
#include "sio.h"
|
||||
@@ -5293,7 +5296,9 @@ static void handle_geom_correction(int id, const char *json)
|
||||
"\"tri_precise\":%llu,\"tri_mixed\":%llu,\"tri_native\":%llu,"
|
||||
"\"rect_bypass\":%llu,\"rect_partial\":%llu,"
|
||||
"\"nclip_precise\":%llu,\"nclip_disagree\":%llu,"
|
||||
"\"nclip_corrected\":%llu}}",
|
||||
"\"nclip_corrected\":%llu,"
|
||||
"\"word\":{\"lookups\":%llu,\"hit\":%llu,\"untracked\":%llu,"
|
||||
"\"mismatch\":%llu,\"partial\":%llu,\"no_z\":%llu}}}",
|
||||
id,
|
||||
gte_geometry_correction_enabled(),
|
||||
gpu_texture_correction_enabled(),
|
||||
@@ -5325,7 +5330,81 @@ static void handle_geom_correction(int id, const char *json)
|
||||
(unsigned long long)ps.rect_partial,
|
||||
(unsigned long long)ps.nclip_precise,
|
||||
(unsigned long long)ps.nclip_disagree,
|
||||
(unsigned long long)ps.nclip_corrected);
|
||||
(unsigned long long)ps.nclip_corrected,
|
||||
(unsigned long long)ps.word_lookups,
|
||||
(unsigned long long)ps.word_hit,
|
||||
(unsigned long long)ps.word_untracked,
|
||||
(unsigned long long)ps.word_mismatch,
|
||||
(unsigned long long)ps.word_partial,
|
||||
(unsigned long long)ps.word_no_z);
|
||||
}
|
||||
|
||||
/* pgxp_shadow — read PGXP shadow slots: {"cmd":"pgxp_shadow","addr":A,
|
||||
* "count":N} walks N guest words from A (RAM / scratchpad); "space":"gpr" or
|
||||
* "gte" with "index"/"count" reads register shadows instead. Each slot: the
|
||||
* word it describes, whether it is live this generation, the per-half/depth
|
||||
* flags (1 = X, 2 = Y, 4 = Z) and the 16.16 positions. Pair it with read_ram
|
||||
* to see whether a packet word still matches its shadow. */
|
||||
static void handle_pgxp_shadow(int id, const char *json)
|
||||
{
|
||||
char sp[16] = "";
|
||||
json_get_str(json, "space", sp, sizeof sp);
|
||||
int space = strcmp(sp, "gpr") == 0 ? 1 : strcmp(sp, "gte") == 0 ? 2 : 0;
|
||||
char addr_str[32] = "";
|
||||
uint32_t key = space ? (uint32_t)json_get_int(json, "index", 0)
|
||||
: (json_get_str(json, "addr", addr_str, sizeof addr_str)
|
||||
? hex_to_u32(addr_str) : 0u);
|
||||
int count = json_get_int(json, "count", 1);
|
||||
if (count < 1) count = 1;
|
||||
if (count > 256) count = 256;
|
||||
static char buf[256 * 112 + 128];
|
||||
size_t n = (size_t)snprintf(buf, sizeof buf, "{\"id\":%d,\"ok\":true,\"slots\":[", id);
|
||||
for (int i = 0; i < count && n < sizeof buf - 128; i++) {
|
||||
uint32_t k = space ? key + (uint32_t)i : key + (uint32_t)i * 4u;
|
||||
int live = 0; uint32_t value = 0, flags = 0; int32_t x16 = 0, y16 = 0; uint16_t z = 0;
|
||||
if (!pgxp_debug_shadow(space, k, &live, &value, &flags, &x16, &y16, &z))
|
||||
break;
|
||||
n += (size_t)snprintf(buf + n, sizeof buf - n,
|
||||
"%s{\"key\":\"0x%08X\",\"live\":%d,\"value\":\"0x%08X\","
|
||||
"\"flags\":%u,\"x16\":%d,\"y16\":%d,\"z\":%u}",
|
||||
i ? "," : "", k, live, value, flags, x16, y16, z);
|
||||
}
|
||||
snprintf(buf + n, sizeof buf - n, "]}");
|
||||
send_fmt("%s", buf);
|
||||
}
|
||||
|
||||
/* pgxp_miss_ring — the newest refused perspective-depth lookups (always-on
|
||||
* ring, PGXP_MISS_RING_CAP deep): {"cmd":"pgxp_miss_ring","count":N}. Each
|
||||
* entry: packet word address (canonical RAM offset), the word the GPU
|
||||
* consumed, the word/flags the shadow held, and the reason (untracked,
|
||||
* mismatch, partial, no_z). Join addr with wtrace_dump to name the writer. */
|
||||
static void handle_pgxp_miss_ring(int id, const char *json)
|
||||
{
|
||||
static const char *const reasons[] = { "?", "untracked", "mismatch",
|
||||
"partial", "no_z" };
|
||||
const PGXPWordMiss *ring; uint32_t cap;
|
||||
uint64_t total = pgxp_word_miss_ring(&ring, &cap);
|
||||
int count = json_get_int(json, "count", 256);
|
||||
uint64_t avail = total < cap ? total : cap;
|
||||
if (count < 0) count = 0;
|
||||
if ((uint64_t)count > avail) count = (int)avail;
|
||||
size_t sz = (size_t)count * 160u + 256u;
|
||||
char *buf = (char *)malloc(sz);
|
||||
if (!buf) { send_err(id, "oom"); return; }
|
||||
size_t n = (size_t)snprintf(buf, sz, "{\"id\":%d,\"ok\":true,\"total\":%llu,\"entries\":[",
|
||||
id, (unsigned long long)total);
|
||||
for (int i = 0; i < count; i++) {
|
||||
const PGXPWordMiss *m = &ring[(total - (uint64_t)count + (uint64_t)i) % cap];
|
||||
n += (size_t)snprintf(buf + n, sz - n,
|
||||
"%s{\"seq\":%llu,\"addr\":\"0x%08X\",\"packet\":\"0x%08X\","
|
||||
"\"shadow\":\"0x%08X\",\"flags\":%u,\"live\":%u,\"reason\":\"%s\"}",
|
||||
i ? "," : "", (unsigned long long)m->seq, m->addr, m->packet,
|
||||
m->shadow_value, m->shadow_flags, m->live,
|
||||
reasons[m->reason < 5 ? m->reason : 0]);
|
||||
}
|
||||
snprintf(buf + n, sz - n, "]}");
|
||||
send_fmt("%s", buf);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* pgxp — live-tune the value-propagation engine for one-toggle isolation runs
|
||||
@@ -7880,6 +7959,33 @@ static void handle_ws_hud_mode(int id, const char *json)
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"tag_rects\":%d}", id, v ? 1 : 0);
|
||||
}
|
||||
|
||||
/* Explicit HUD-anchor / background tag pipeline counters (always on):
|
||||
* {"cmd":"ws_tag_stats"}
|
||||
* -> hud / background: tag_calls, tag_rejected, hit, stale, guard_mismatch,
|
||||
* last_tag_frame, last_hit_frame. A title plugin that tags but never hits
|
||||
* is either tagging the wrong address or rewriting packets after tagging. */
|
||||
static void handle_ws_tag_stats(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
GpuWsTagStats hud, bg;
|
||||
gpu_ws_get_tag_stats(&hud, &bg);
|
||||
const GpuWsTagStats *s[2] = { &hud, &bg };
|
||||
char part[2][256];
|
||||
for (int i = 0; i < 2; i++)
|
||||
snprintf(part[i], sizeof part[i],
|
||||
"{\"tag_calls\":%llu,\"tag_rejected\":%llu,\"hit\":%llu,"
|
||||
"\"stale\":%llu,\"guard_mismatch\":%llu,"
|
||||
"\"last_tag_frame\":%u,\"last_hit_frame\":%u}",
|
||||
(unsigned long long)s[i]->tag_calls,
|
||||
(unsigned long long)s[i]->tag_rejected,
|
||||
(unsigned long long)s[i]->hit,
|
||||
(unsigned long long)s[i]->stale,
|
||||
(unsigned long long)s[i]->guard_mismatch,
|
||||
s[i]->last_tag_frame, s[i]->last_hit_frame);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"frame\":%llu,\"hud\":%s,\"background\":%s}",
|
||||
id, (unsigned long long)s_frame_count, part[0], part[1]);
|
||||
}
|
||||
|
||||
/* Kernel-image bless state: {"cmd":"kernel_bless"} ->
|
||||
* entries/clean/mismatch/native_hits/verifies/invalidations, plus the
|
||||
* declared kernel patch ranges and the segments they made the verifier
|
||||
@@ -8156,11 +8262,18 @@ 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_err(id,"invalid display aspect (4:3 or wider, terms 1..99)");return;
|
||||
}
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"num\":%d,\"den\":%d,\"adaptive\":%d}",id,num,den,adaptive!=0);
|
||||
}
|
||||
|
||||
extern int psx_debug_window_size(int w, int h);
|
||||
static void handle_window_size(int id, const char *json) {
|
||||
int w = json_get_int(json, "w", -1), h = json_get_int(json, "h", -1);
|
||||
if (!psx_debug_window_size(w, h)) { send_err(id, "need a window and 64<=w,h<=16384"); return; }
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"w\":%d,\"h\":%d}", id, w, h);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
@@ -8882,6 +8995,12 @@ typedef struct {
|
||||
uint16_t *vram; /* full 1024x512 */
|
||||
} DispRingEntry;
|
||||
static PSX_BSS DispRingEntry s_disp_ring[DISP_RING_CAP];
|
||||
/* Capture mark state (debug_server_capture_mark / capture_mark). */
|
||||
static int s_capture_frozen = 0;
|
||||
static uint64_t s_capture_mark_frame = 0;
|
||||
static uint64_t s_capture_mark_count = 0;
|
||||
static char s_capture_wide_path[512];
|
||||
static int s_capture_wide_ok = 0;
|
||||
static uint16_t *s_disp_ring_px = NULL; /* one block for all entries */
|
||||
|
||||
static void disp_ring_capture(void)
|
||||
@@ -8895,7 +9014,7 @@ static void disp_ring_capture(void)
|
||||
const char *e = getenv("PSX_DISPLAY_RING");
|
||||
enabled = (!e || !*e || *e != '0') ? 1 : 0;
|
||||
}
|
||||
if (!enabled) return;
|
||||
if (!enabled || s_capture_frozen) return;
|
||||
if (!s_disp_ring_px) {
|
||||
size_t per = (size_t)DISP_RING_MAX_W * DISP_RING_MAX_H;
|
||||
size_t vram = (size_t)1024 * 512;
|
||||
@@ -8913,6 +9032,7 @@ static void disp_ring_capture(void)
|
||||
GpuDisplayInfo di;
|
||||
gpu_get_display_info(&di);
|
||||
if (di.disabled || di.width == 0 || di.height == 0) return;
|
||||
gpu_timeline_note(GTL_DISP_CAPTURE, 0, di.display_x | (di.display_y << 16));
|
||||
uint32_t w = di.width, h = di.height;
|
||||
if (w > DISP_RING_MAX_W) w = DISP_RING_MAX_W;
|
||||
if (h > DISP_RING_MAX_H) h = DISP_RING_MAX_H;
|
||||
@@ -9350,7 +9470,10 @@ static void handle_dump_buffer(int id, const char *json)
|
||||
* present path uses (gr_render_wide_display into a scratch buffer), so the dump
|
||||
* reflects the composited wide frame 1:1, orientation included. Errors if the
|
||||
* active backend has no wide compositor or native-wide isn't engaged. */
|
||||
static void handle_wide_shot(int id, const char *json)
|
||||
/* Write the presented native-wide frame to `path`. Returns NULL on success
|
||||
* (and the pixel size), or a static error string. Shared by wide_shot and
|
||||
* the capture mark. */
|
||||
static const char *write_wide_png(const char *path, int *out_w, int *out_h)
|
||||
{
|
||||
extern int gr_wide_supported(void);
|
||||
extern int gr_scale(void);
|
||||
@@ -9359,33 +9482,28 @@ static void handle_wide_shot(int id, const char *json)
|
||||
extern void gl_renderer_sync_cpu(void);
|
||||
gl_renderer_sync_cpu(); /* no-op on SW / when no GL frame pending */
|
||||
|
||||
if (!gr_wide_supported()) { send_err(id, "active backend has no wide compositor"); return; }
|
||||
if (!gr_wide_supported()) return "active backend has no wide compositor";
|
||||
int extra = ws_nw_extra();
|
||||
if (extra <= 0) { send_err(id, "native-wide not engaged (extra=0; need a wide game frame)"); return; }
|
||||
if (extra <= 0) return "native-wide not engaged (extra=0; need a wide game frame)";
|
||||
|
||||
GpuDisplayInfo di;
|
||||
gpu_get_display_info(&di);
|
||||
if (di.disabled || di.width == 0 || di.height == 0) { send_err(id, "display disabled"); return; }
|
||||
if (di.disabled || di.width == 0 || di.height == 0) return "display disabled";
|
||||
|
||||
int scale = gr_scale(); if (scale < 1) scale = 1;
|
||||
int present_w = (int)di.width + extra;
|
||||
int W = present_w * scale, H = (int)di.height * scale;
|
||||
|
||||
char path[512];
|
||||
if (!json_get_str(json, "path", path, sizeof(path)))
|
||||
strncpy(path, "psx_wide.png", sizeof(path) - 1);
|
||||
path[sizeof(path) - 1] = '\0';
|
||||
|
||||
uint32_t *buf = (uint32_t *)malloc((size_t)W * H * sizeof(uint32_t));
|
||||
if (!buf) { send_err(id, "alloc failed"); return; }
|
||||
if (!buf) return "alloc failed";
|
||||
int n = gr_render_wide_display(buf, W * (int)sizeof(uint32_t),
|
||||
(int)di.display_x, (int)di.display_y, (int)di.height);
|
||||
if (n <= 0) { free(buf); send_err(id, "no wide surface for displayed buffer"); return; }
|
||||
if (n <= 0) { free(buf); return "no wide surface for displayed buffer"; }
|
||||
|
||||
/* ARGB8888 (0xAARRGGBB) -> RGB, written in the buffer's row order (so the
|
||||
* PNG shows exactly the present orientation — the point of this probe). */
|
||||
uint8_t *rgb = (uint8_t *)malloc((size_t)W * H * 3);
|
||||
if (!rgb) { free(buf); send_err(id, "alloc failed"); return; }
|
||||
if (!rgb) { free(buf); return "alloc failed"; }
|
||||
for (int y = 0; y < H; y++) {
|
||||
for (int x = 0; x < W; x++) {
|
||||
uint32_t px = buf[(size_t)y * W + x];
|
||||
@@ -9397,14 +9515,114 @@ static void handle_wide_shot(int id, const char *json)
|
||||
}
|
||||
free(buf);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) { free(rgb); send_err(id, "cannot open file"); return; }
|
||||
if (!f) { free(rgb); return "cannot open file"; }
|
||||
int ok = png_write_rgb(f, rgb, (uint32_t)W, (uint32_t)H);
|
||||
free(rgb); fclose(f);
|
||||
if (!ok) { send_err(id, "png encode failed"); return; }
|
||||
if (!ok) return "png encode failed";
|
||||
if (out_w) *out_w = W;
|
||||
if (out_h) *out_h = H;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void handle_wide_shot(int id, const char *json)
|
||||
{
|
||||
char path[512];
|
||||
if (!json_get_str(json, "path", path, sizeof(path)))
|
||||
strncpy(path, "psx_wide.png", sizeof(path) - 1);
|
||||
path[sizeof(path) - 1] = '\0';
|
||||
int W = 0, H = 0;
|
||||
const char *err = write_wide_png(path, &W, &H);
|
||||
if (err) { send_err(id, err); return; }
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"path\":\"%s\",\"width\":%d,\"height\":%d}",
|
||||
id, path, W, H);
|
||||
}
|
||||
|
||||
/* Operator capture mark. Runs on the present thread (hotkey handler), where
|
||||
* the renderer's context is current. Freezes the display and GP0 rings so the
|
||||
* frames and draw lists leading up to the mark survive until read, and saves
|
||||
* the presented native-wide frame (the only record of the compositor output).
|
||||
* A second mark while frozen is ignored; release with capture_mark op=release. */
|
||||
void debug_server_capture_mark(void)
|
||||
{
|
||||
if (s_capture_frozen) return;
|
||||
s_capture_mark_frame = s_frame_count;
|
||||
s_capture_mark_count++;
|
||||
snprintf(s_capture_wide_path, sizeof s_capture_wide_path,
|
||||
"capture_mark_%llu_wide.png", (unsigned long long)s_frame_count);
|
||||
s_capture_wide_ok = write_wide_png(s_capture_wide_path, NULL, NULL) == NULL;
|
||||
s_capture_frozen = 1;
|
||||
gpu_gp0_ring_set_frozen(1);
|
||||
present_image_ring_set_frozen(1);
|
||||
gpu_timeline_set_frozen(1);
|
||||
}
|
||||
|
||||
/* Presented-image ring (see present_image_ring.h):
|
||||
* {"cmd":"present_image_ring_stats"} -> oldest, newest, count, frozen
|
||||
* {"cmd":"present_image_ring_get","frame":N,"path":"x.png"} */
|
||||
static void handle_present_image_ring_stats(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
uint32_t lo = 0, hi = 0; int n = 0;
|
||||
present_image_ring_span(&lo, &hi, &n);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"oldest\":%u,\"newest\":%u,\"count\":%d,"
|
||||
"\"frozen\":%d,\"capacity\":%d,\"height\":%d}",
|
||||
id, lo, hi, n, present_image_ring_frozen(),
|
||||
PRESENT_IMAGE_RING_CAP, PRESENT_IMAGE_RING_H);
|
||||
}
|
||||
static void handle_present_image_ring_get(int id, const char *json)
|
||||
{
|
||||
int f = json_get_int(json, "frame", -1);
|
||||
char path[512];
|
||||
if (f < 0 || !json_get_str(json, "path", path, sizeof path)) {
|
||||
send_err(id, "need frame and path"); return;
|
||||
}
|
||||
uint8_t *rgb = NULL; int w = 0, h = 0;
|
||||
if (!present_image_ring_get_rgb((uint32_t)f, &rgb, &w, &h)) {
|
||||
send_err(id, "frame not in presented-image ring"); return;
|
||||
}
|
||||
FILE *fp = fopen(path, "wb");
|
||||
int ok = fp && png_write_rgb(fp, rgb, (uint32_t)w, (uint32_t)h);
|
||||
if (fp) fclose(fp);
|
||||
free(rgb);
|
||||
if (!ok) { send_err(id, "png write failed"); return; }
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"frame\":%d,\"width\":%d,\"height\":%d}",
|
||||
id, f, w, h);
|
||||
}
|
||||
|
||||
/* {"cmd":"capture_mark","op":"status"|"mark"|"release"}
|
||||
* status -> frozen, mark_frame, count, wide_path/wide_ok, and the frozen
|
||||
* display-ring / GP0-ring frame spans to query.
|
||||
* mark -> same as the hotkey (use only when nobody is at the window).
|
||||
* release -> resume continuous capture. */
|
||||
static void handle_capture_mark(int id, const char *json)
|
||||
{
|
||||
char op[16] = "status";
|
||||
(void)json_get_str(json, "op", op, sizeof op);
|
||||
if (!strcmp(op, "mark")) debug_server_capture_mark();
|
||||
else if (!strcmp(op, "release")) {
|
||||
s_capture_frozen = 0;
|
||||
gpu_gp0_ring_set_frozen(0);
|
||||
present_image_ring_set_frozen(0);
|
||||
gpu_timeline_set_frozen(0);
|
||||
} else if (strcmp(op, "status")) { send_err(id, "op must be status|mark|release"); return; }
|
||||
uint32_t oldest = 0, newest = 0;
|
||||
gpu_gp0_ring_frame_span(&oldest, &newest);
|
||||
uint32_t d_old = UINT32_MAX, d_new = 0;
|
||||
for (int i = 0; i < DISP_RING_CAP; i++) {
|
||||
if (!s_disp_ring[i].valid) continue;
|
||||
if (s_disp_ring[i].frame < d_old) d_old = s_disp_ring[i].frame;
|
||||
if (s_disp_ring[i].frame > d_new) d_new = s_disp_ring[i].frame;
|
||||
}
|
||||
if (d_old == UINT32_MAX) d_old = 0;
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"frozen\":%d,\"mark_frame\":%llu,"
|
||||
"\"count\":%llu,\"wide_path\":\"%s\",\"wide_ok\":%d,"
|
||||
"\"display_ring\":[%u,%u],\"gp0_ring\":[%u,%u],\"frame\":%llu}",
|
||||
id, s_capture_frozen, (unsigned long long)s_capture_mark_frame,
|
||||
(unsigned long long)s_capture_mark_count, s_capture_wide_path,
|
||||
s_capture_wide_ok, d_old, d_new, oldest, newest,
|
||||
(unsigned long long)s_frame_count);
|
||||
}
|
||||
|
||||
/* screenshot: capture what the player is actually being shown. Native-wide
|
||||
* presentation is wider than the canonical PSX display rectangle, so routing
|
||||
* this command unconditionally through handle_screenshot_file silently omits
|
||||
@@ -9687,6 +9905,87 @@ static void handle_present_ring(int id, const char *json)
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* Precise-NCLIP attribution (gte_nclip_stats.h).
|
||||
* {"cmd":"nclip_stats"} totals + per-function + per-site tables
|
||||
* {"cmd":"nclip_stats","reset":1} clear the tables after reading
|
||||
* {"cmd":"nclip_stats","exact":0|1} switch exact-site consumers off/on
|
||||
* funcs: [near_store_pc, nclips, disagree, saturated, last_frame] (1 = unknown)
|
||||
* sites: [pc, evals, flips, fallbacks, last_frame] */
|
||||
static void handle_nclip_stats(int id, const char *json)
|
||||
{
|
||||
extern int gpu_ws_precise_nclip_enabled(void);
|
||||
static GteNclipFuncStat funcs[GTE_NCLIP_STAT_CAP];
|
||||
static GteNclipSiteStat sites[GTE_NCLIP_STAT_CAP];
|
||||
uint64_t hits = 0, fallbacks = 0, disagree = 0;
|
||||
gte_nclip_precise_stats(&hits, &fallbacks, &disagree);
|
||||
const int nf = gte_nclip_func_stats(funcs, GTE_NCLIP_STAT_CAP);
|
||||
const int ns = gte_nclip_site_stats(sites, GTE_NCLIP_STAT_CAP);
|
||||
const int bufsz = 256 + (nf + ns) * 72;
|
||||
char *buf = (char *)malloc((size_t)bufsz);
|
||||
if (!buf) { send_err(id, "alloc failed"); return; }
|
||||
int pos = snprintf(buf, bufsz,
|
||||
"{\"id\":%d,\"ok\":true,\"enabled\":%d,\"exact\":%d,\"hits\":%llu,\"fallbacks\":%llu,"
|
||||
"\"disagreements\":%llu,\"funcs\":[",
|
||||
id, gpu_ws_precise_nclip_enabled(), gte_nclip_exact_enabled(),
|
||||
(unsigned long long)hits,
|
||||
(unsigned long long)fallbacks, (unsigned long long)disagree);
|
||||
for (int i = 0; i < nf; i++)
|
||||
pos += snprintf(buf + pos, bufsz - pos, "%s[\"0x%08X\",%u,%u,%u,%u]",
|
||||
i ? "," : "", funcs[i].func, funcs[i].nclips,
|
||||
funcs[i].disagree, funcs[i].saturated, funcs[i].last_frame);
|
||||
pos += snprintf(buf + pos, bufsz - pos, "],\"sites\":[");
|
||||
for (int i = 0; i < ns; i++)
|
||||
pos += snprintf(buf + pos, bufsz - pos, "%s[\"0x%08X\",%u,%u,%u,%u]",
|
||||
i ? "," : "", sites[i].pc, sites[i].evals,
|
||||
sites[i].flips, sites[i].fallbacks, sites[i].last_frame);
|
||||
snprintf(buf + pos, bufsz - pos, "]}");
|
||||
if (json_get_int(json, "reset", 0)) gte_nclip_stats_reset();
|
||||
{
|
||||
const int exact = json_get_int(json, "exact", -1);
|
||||
if (exact >= 0) gte_nclip_exact_set_enabled(exact);
|
||||
}
|
||||
send_fmt("%s", buf);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* GPU frame timeline (gpu_timeline.h): cycle-ordered vblank / flip /
|
||||
* draw-area / linked-list DMA / present events.
|
||||
* {"cmd":"gpu_timeline","from":F,"to":T} frames [F,T] (default: last 8)
|
||||
* events: [seq, cycle, frame, kind, a, b] */
|
||||
static void handle_gpu_timeline(int id, const char *json)
|
||||
{
|
||||
uint64_t total = gpu_timeline_total();
|
||||
uint64_t oldest = total > GPU_TIMELINE_CAP ? total - GPU_TIMELINE_CAP : 0;
|
||||
GpuTimelineEntry last;
|
||||
uint32_t newest_frame = (total && gpu_timeline_get(total - 1, &last)) ? last.frame : 0;
|
||||
int to = json_get_int(json, "to", (int)newest_frame);
|
||||
int from = json_get_int(json, "from", to - 7);
|
||||
if (from < 0) from = 0;
|
||||
if (to < from) { send_err(id, "to < from"); return; }
|
||||
int bufsz = 256 + 4096 * 80;
|
||||
char *buf = (char *)malloc((size_t)bufsz);
|
||||
if (!buf) { send_err(id, "alloc failed"); return; }
|
||||
GpuTimelineEntry first_e;
|
||||
uint32_t oldest_frame = (total && gpu_timeline_get(oldest, &first_e)) ? first_e.frame : 0;
|
||||
int pos = snprintf(buf, bufsz,
|
||||
"{\"id\":%d,\"ok\":true,\"total\":%llu,\"frames\":[%u,%u],\"events\":[",
|
||||
id, (unsigned long long)total, oldest_frame, newest_frame);
|
||||
int first = 1, n = 0;
|
||||
for (uint64_t s = oldest; s < total && pos < bufsz - 128 && n < 4096; s++) {
|
||||
GpuTimelineEntry e;
|
||||
if (!gpu_timeline_get(s, &e)) continue;
|
||||
if ((int)e.frame < from || (int)e.frame > to) continue;
|
||||
pos += snprintf(buf + pos, bufsz - pos, "%s[%llu,%llu,%u,\"%s\",%u,%u]",
|
||||
first ? "" : ",", (unsigned long long)s,
|
||||
(unsigned long long)e.cyc, e.frame,
|
||||
gpu_timeline_kind_name(e.kind), e.a, e.b);
|
||||
first = 0; n++;
|
||||
}
|
||||
pos += snprintf(buf + pos, bufsz - pos, "],\"truncated\":%s}", n >= 4096 ? "true" : "false");
|
||||
send_fmt("%s", buf);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* ---- Write trace: hook + handlers (Tier 1 reverse debugger) ---- */
|
||||
extern CPUState *debug_cpu_ptr;
|
||||
|
||||
@@ -14194,13 +14493,20 @@ static const CmdEntry s_commands[] = {
|
||||
{ "gpu_state", handle_gpu_state },
|
||||
{ "geom_correction", handle_geom_correction },
|
||||
{ "pgxp", handle_pgxp },
|
||||
{ "pgxp_shadow", handle_pgxp_shadow },
|
||||
{ "pgxp_miss_ring", handle_pgxp_miss_ring },
|
||||
{ "ws_aspect_cone_site", handle_ws_aspect_cone_site },
|
||||
{ "ws_margin", handle_ws_margin },
|
||||
{ "ws_hud_mode", handle_ws_hud_mode },
|
||||
{ "ws_tag_stats", handle_ws_tag_stats },
|
||||
{ "mod_counters", handle_mod_counters },
|
||||
{ "capture_mark", handle_capture_mark },
|
||||
{ "present_image_ring_stats", handle_present_image_ring_stats },
|
||||
{ "present_image_ring_get", handle_present_image_ring_get },
|
||||
{ "kernel_bless", handle_kernel_bless },
|
||||
{ "ws_aspect", handle_ws_aspect },
|
||||
{ "display_aspect", handle_display_aspect },
|
||||
{ "window_size", handle_window_size },
|
||||
{ "ws_nw", handle_ws_nw },
|
||||
{ "scanline", handle_scanline },
|
||||
{ "ws_backdrop_ring", handle_ws_backdrop_ring },
|
||||
@@ -14218,6 +14524,8 @@ static const CmdEntry s_commands[] = {
|
||||
{ "gl_coh_ring", handle_gl_coh_ring },
|
||||
{ "gl_present_ring", handle_gl_present_ring },
|
||||
{ "present_ring", handle_present_ring },
|
||||
{ "gpu_timeline", handle_gpu_timeline },
|
||||
{ "nclip_stats", handle_nclip_stats },
|
||||
{ "frame_perf", handle_frame_perf },
|
||||
{ "gl_ws_ablate", handle_gl_ws_ablate },
|
||||
{ "gl_interp", handle_gl_interp },
|
||||
|
||||
Reference in New Issue
Block a user