scene: FMV boundary flash = instant emulated yield (stale framebuffer); async video planes
This commit is contained in:
parent
01a8ffbb9a
commit
700e1e2d82
1 changed files with 95 additions and 22 deletions
|
|
@ -2866,6 +2866,12 @@ void OnPhotoReplayUpdate() {
|
||||||
// logos, any full-motion video). Same contract as the photo-replay
|
// logos, any full-motion video). Same contract as the photo-replay
|
||||||
// heartbeat above.
|
// heartbeat above.
|
||||||
static std::atomic<int64_t> g_movie_decode_last_ns{-1};
|
static std::atomic<int64_t> g_movie_decode_last_ns{-1};
|
||||||
|
// Last frame the 2D replay actually SUBSTITUTED a movie quad (ps_yuv2d).
|
||||||
|
// Consulted by YieldForMovie: while the native substitution is serving the
|
||||||
|
// video, the emulated yield must never engage; the emulated framebuffer
|
||||||
|
// is STALE under draw suppression, and presenting it flashed the previous
|
||||||
|
// video's last frame at every video boundary.
|
||||||
|
static std::atomic<int64_t> g_movie_native_last_ns{-1};
|
||||||
|
|
||||||
void OnMovieDecode() {
|
void OnMovieDecode() {
|
||||||
g_movie_decode_last_ns.store(
|
g_movie_decode_last_ns.store(
|
||||||
|
|
@ -13817,18 +13823,37 @@ bool YieldForMovie() {
|
||||||
PerfClock::now().time_since_epoch())
|
PerfClock::now().time_since_epoch())
|
||||||
.count();
|
.count();
|
||||||
const bool active = now_ns - last_ns < 500'000'000;
|
const bool active = now_ns - last_ns < 500'000'000;
|
||||||
|
// LAST-RESORT gating: yielding INSTANTLY at video start presented the
|
||||||
|
// emulated framebuffer, which under draw suppression still holds a stale
|
||||||
|
// frame: the flash of the PREVIOUS video at every video boundary
|
||||||
|
// (measured: yield, planes published +16 ms, substitution
|
||||||
|
// native +38 ms, a ~5-frame stale-frame window). The substitution path
|
||||||
|
// gets 400 ms from the first heartbeat to engage, and once it has drawn
|
||||||
|
// recently the yield stays off entirely (covers the video-END race the
|
||||||
|
// same way). Genuine native-FMV failure (fmv_native off, pso missing,
|
||||||
|
// plane decode failure) still reaches the emulated yield after the
|
||||||
|
// grace window.
|
||||||
|
static int64_t s_fresh_since = -1;
|
||||||
|
if (!active) {
|
||||||
|
s_fresh_since = -1;
|
||||||
|
} else if (s_fresh_since < 0) {
|
||||||
|
s_fresh_since = now_ns;
|
||||||
|
}
|
||||||
|
const int64_t native_ns = g_movie_native_last_ns.load(std::memory_order_relaxed);
|
||||||
|
const bool yield = active && now_ns - s_fresh_since >= 400'000'000 &&
|
||||||
|
(native_ns < 0 || now_ns - native_ns > 1'000'000'000);
|
||||||
static bool s_active = false;
|
static bool s_active = false;
|
||||||
if (active != s_active) {
|
if (yield != s_active) {
|
||||||
s_active = active;
|
s_active = yield;
|
||||||
if (active) {
|
if (yield) {
|
||||||
REXLOG_INFO(
|
REXLOG_INFO(
|
||||||
"native-scene: FMV playing - yielding to emulated output "
|
"native-scene: FMV playing - yielding to emulated output "
|
||||||
"(MovieDecoder heartbeat)");
|
"(MovieDecoder heartbeat; substitution did not engage)");
|
||||||
} else {
|
} else {
|
||||||
REXLOG_INFO("native-scene: FMV ended - native output resumes");
|
REXLOG_INFO("native-scene: FMV ended - native output resumes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return active;
|
return yield;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool YieldForPhotoEditor(uint8_t* base) {
|
bool YieldForPhotoEditor(uint8_t* base) {
|
||||||
|
|
@ -16285,7 +16310,22 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
if (regen < 2 || !EnsureHudTileRegen(context, base, fetch, regen, gt)) {
|
if (regen < 2 || !EnsureHudTileRegen(context, base, fetch, regen, gt)) {
|
||||||
EnsureGuestTextureFromWords(context, base, fetch, gt);
|
EnsureGuestTextureFromWords(context, base, fetch, gt);
|
||||||
}
|
}
|
||||||
g_pw_tex_decode.Add(perf_ns_since(hud_t0));
|
const uint64_t decode_ns = perf_ns_since(hud_t0);
|
||||||
|
g_pw_tex_decode.Add(decode_ns);
|
||||||
|
// Attribution for residual render-thread stalls: anything still
|
||||||
|
// decoding inline for >3 ms should either move over the async
|
||||||
|
// threshold or explain itself here.
|
||||||
|
if (decode_ns > 3'000'000) {
|
||||||
|
static std::atomic<uint32_t> s_slow{0};
|
||||||
|
const uint32_t n = s_slow.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
if (n < 24 || (n & 255u) == 0) {
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: SLOW inline 2D decode {:.1f}ms {}x{} "
|
||||||
|
"fetch=[{:08X} {:08X} {:08X}] forced={} (n={})",
|
||||||
|
double(decode_ns) / 1e6, px_w, px_h, fetch[0], fetch[1],
|
||||||
|
fetch[2], force_inline ? 1 : 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!gt.valid) {
|
if (!gt.valid) {
|
||||||
static std::unordered_set<uint64_t> logged;
|
static std::unordered_set<uint64_t> logged;
|
||||||
if (logged.size() < 32 && logged.insert(key).second) {
|
if (logged.size() < 32 && logged.insert(key).second) {
|
||||||
|
|
@ -16662,8 +16702,17 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
bool movie_drawn = false;
|
bool movie_drawn = false;
|
||||||
for (const Draw2d& d : scene_2d) {
|
for (const Draw2d& d : scene_2d) {
|
||||||
const uint32_t* yuv = nullptr;
|
const uint32_t* yuv = nullptr;
|
||||||
if (movie_sub && d.composite_srv < 0) {
|
// Video-quad detection runs regardless of movie_sub: a quad whose
|
||||||
if (yuv_triple(d)) {
|
// own fetch slots form a YUV triple must NEVER draw through ps_main
|
||||||
|
// - that renders the raw Y plane (greyscale luma, or the PREVIOUS
|
||||||
|
// video's frame while the plane copies are still stale), the
|
||||||
|
// video-boundary flash class. If the planes can't resolve yet
|
||||||
|
// (async decode in flight, substitution off) the quad is SKIPPED;
|
||||||
|
// black under a starting video is what the real thing looks like.
|
||||||
|
bool video_quad = false;
|
||||||
|
if (d.composite_srv < 0 && yuv_triple(d)) {
|
||||||
|
video_quad = true;
|
||||||
|
if (movie_sub) {
|
||||||
TripleCacheEntry* e = nullptr;
|
TripleCacheEntry* e = nullptr;
|
||||||
for (int t = 0; t < triple_count && e == nullptr; ++t) {
|
for (int t = 0; t < triple_count && e == nullptr; ++t) {
|
||||||
if (triple_cache[t].y_addr == d.fetch[1]) {
|
if (triple_cache[t].y_addr == d.fetch[1]) {
|
||||||
|
|
@ -16674,18 +16723,24 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
e = &triple_cache[triple_count++];
|
e = &triple_cache[triple_count++];
|
||||||
e->y_addr = d.fetch[1];
|
e->y_addr = d.fetch[1];
|
||||||
e->ok = true;
|
e->ok = true;
|
||||||
|
bool decode_failed = false;
|
||||||
for (int p = 0; p < 3 && e->ok; ++p) {
|
for (int p = 0; p < 3 && e->ok; ++p) {
|
||||||
const uint32_t* w = d.fetch + p * 6;
|
const uint32_t* w = d.fetch + p * 6;
|
||||||
auto hot = find_words_texture(FetchWordsKey(w));
|
auto hot = find_words_texture(FetchWordsKey(w));
|
||||||
if (hot != g_r.tex_store.end()) {
|
if (hot != g_r.tex_store.end()) {
|
||||||
hot->second.recheck_frame = 0; // content-hot: per-frame probe
|
hot->second.recheck_frame = 0; // content-hot: per-frame probe
|
||||||
}
|
}
|
||||||
const GuestTexture* t =
|
// Async-capable resolve: a content change serves the stale
|
||||||
resolve_2d_texture(w, /*force_inline=*/true);
|
// plane while the worker re-decodes (the per-frame inline
|
||||||
e->ok = t != &g_r.white && t->texture != nullptr;
|
// re-decode of a 720p plane set cost ~3x4 ms on the render
|
||||||
e->slots[p] = t->srv_slot;
|
// thread every content change: video judder + boot
|
||||||
|
// sluggishness); nullptr = first decode still in flight.
|
||||||
|
const GuestTexture* t = resolve_2d_texture(w);
|
||||||
|
e->ok = t != nullptr && t != &g_r.white && t->texture != nullptr;
|
||||||
|
decode_failed |= t == &g_r.white;
|
||||||
|
e->slots[p] = e->ok ? t->srv_slot : 0;
|
||||||
}
|
}
|
||||||
if (!e->ok) {
|
if (decode_failed) {
|
||||||
static std::atomic<uint32_t> s_triple_failed{0};
|
static std::atomic<uint32_t> s_triple_failed{0};
|
||||||
if (s_triple_failed.fetch_add(1, std::memory_order_relaxed) < 8) {
|
if (s_triple_failed.fetch_add(1, std::memory_order_relaxed) < 8) {
|
||||||
REXLOG_INFO(
|
REXLOG_INFO(
|
||||||
|
|
@ -16699,7 +16754,12 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
yuv = e->slots;
|
yuv = e->slots;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (yuv == nullptr && (d.flags & 0x2u) != 0 && d.src_stride == 24) {
|
}
|
||||||
|
if (yuv == nullptr && movie_sub && d.composite_srv < 0 &&
|
||||||
|
(d.flags & 0x2u) != 0 && d.src_stride == 24) {
|
||||||
|
// Bracketed movie quad without a readable triple: through ps_main
|
||||||
|
// it draws its c8 opaque-black cover, also a video quad.
|
||||||
|
video_quad = true;
|
||||||
yuv = fallback_yuv();
|
yuv = fallback_yuv();
|
||||||
if (yuv != nullptr) {
|
if (yuv != nullptr) {
|
||||||
static std::atomic<uint32_t> s_fb_logged{0};
|
static std::atomic<uint32_t> s_fb_logged{0};
|
||||||
|
|
@ -16710,10 +16770,23 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (video_quad && yuv == nullptr) {
|
||||||
|
static std::atomic<uint32_t> s_vskip{0};
|
||||||
|
const uint32_t n = s_vskip.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
if (n < 8 || (n & 511u) == 0) {
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: video quad skipped (planes not ready, "
|
||||||
|
"y={:08X}, movie_sub={}) (n={})",
|
||||||
|
d.fetch[1], movie_sub ? 1 : 0, n);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
emit_draw(d, yuv);
|
emit_draw(d, yuv);
|
||||||
movie_drawn |= yuv != nullptr;
|
movie_drawn |= yuv != nullptr;
|
||||||
}
|
}
|
||||||
|
if (movie_drawn) {
|
||||||
|
g_movie_native_last_ns.store(movie_now_ns, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
if (movie_drawn) {
|
if (movie_drawn) {
|
||||||
static std::atomic<bool> s_movie_logged{false};
|
static std::atomic<bool> s_movie_logged{false};
|
||||||
if (!s_movie_logged.exchange(true, std::memory_order_relaxed)) {
|
if (!s_movie_logged.exchange(true, std::memory_order_relaxed)) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue