photo: shutter burst at the grab request - display card native
This commit is contained in:
parent
0a73e321de
commit
7e6d318a52
3 changed files with 257 additions and 4 deletions
|
|
@ -48,6 +48,17 @@ REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_frames, 4, "Skate 3",
|
||||||
"the snapshot")
|
"the snapshot")
|
||||||
.range(1, 600)
|
.range(1, 600)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
// Defined in skate3_native_scene.cpp (the recording filter lives there).
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_snapshot_all_draws);
|
||||||
|
REXCVAR_DEFINE_BOOL(
|
||||||
|
skate3_native_render_photo_compose_trace, false, "Skate 3",
|
||||||
|
"Auto-capture an F10-style diagnostic recording (all draws, ~360 "
|
||||||
|
"frames + memory snapshot) the first time a photo display card comes "
|
||||||
|
"up in a session. Captures the game's one-shot framed-card compose "
|
||||||
|
"pass: its draws, source textures (frame art / logo / caption) and "
|
||||||
|
"geometry, for offline analysis. Writes ~100 MB into "
|
||||||
|
"native_render_snapshots once per session.")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_stride, 1, "Skate 3",
|
REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_stride, 1, "Skate 3",
|
||||||
"Record every Nth frame while collecting (long viewer recordings: "
|
"Record every Nth frame while collecting (long viewer recordings: "
|
||||||
"e.g. 12 = ~12 recorded frames/sec at 144fps)")
|
"e.g. 12 = ~12 recorded frames/sec at 144fps)")
|
||||||
|
|
@ -600,6 +611,28 @@ void OnFrameEnd(uint8_t* base) {
|
||||||
g_frame_index, mesh_count);
|
g_frame_index, mesh_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Photo display-card compose auto-trace: the first time the framed card
|
||||||
|
// comes up in a session, record EVERY draw for ~360 frames + the memory
|
||||||
|
// snapshot; the game's one-shot card compose (frame art / logo / caption
|
||||||
|
// stamped over the photo) fires at an unpredictable moment within this
|
||||||
|
// window, and the capture pins its draws, source textures and geometry.
|
||||||
|
if (!g_collecting && REXCVAR_GET(skate3_native_render_photo_compose_trace)) {
|
||||||
|
static bool s_compose_trace_done = false;
|
||||||
|
if (!s_compose_trace_done && skate3::native_scene::PhotoCardVisible()) {
|
||||||
|
s_compose_trace_done = true;
|
||||||
|
REXCVAR_SET(skate3_native_render_snapshot_frames, 360);
|
||||||
|
REXCVAR_SET(skate3_native_render_snapshot_stride, 1);
|
||||||
|
REXCVAR_SET(skate3_native_render_snapshot_all_draws, true);
|
||||||
|
g_collecting = true;
|
||||||
|
g_collect_counter = 0;
|
||||||
|
skate3::native_scene::StartRecording(1);
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-render snapshot: photo display card up - compose trace "
|
||||||
|
"armed at frame {} (360 frames, all draws; snapshot cvars stay "
|
||||||
|
"changed for this session)",
|
||||||
|
g_frame_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (g_collecting) {
|
if (g_collecting) {
|
||||||
// Immediate (F10) captures skip the arming frame: the scene-side
|
// Immediate (F10) captures skip the arming frame: the scene-side
|
||||||
// recording was only armed after this frame's BuildFrameScene ran, so
|
// recording was only armed after this frame's BuildFrameScene ran, so
|
||||||
|
|
@ -852,6 +885,27 @@ extern "C" REX_FUNC(sub_824FD550) {
|
||||||
skate3::native_scene::OnTakePhoto();
|
skate3::native_scene::OnTakePhoto();
|
||||||
}
|
}
|
||||||
__imp__sub_824FD550(ctx, base);
|
__imp__sub_824FD550(ctx, base);
|
||||||
|
// Post-call: the grab has CPU-read the shot; the card compose that
|
||||||
|
// follows reads already-copied memory. Event-closes the shutter burst.
|
||||||
|
if (skate3::native_render::Enabled()) {
|
||||||
|
skate3::native_scene::OnPhotoGrabDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sk8::BE::ScreenshotBackEnd grab REQUEST (sets the grab params + arms the
|
||||||
|
// request flag; called by the FE photo flows 1-2 frames BEFORE the game
|
||||||
|
// renders the shot + card-composite frame sequence whose resolves
|
||||||
|
// GrabScreenshot/OnScreenShot then CPU-read). This is the only PREDICTIVE
|
||||||
|
// shutter signal; the GrabScreenshot hook above fires AFTER those frames
|
||||||
|
// already rendered (suppressed). Arms the shutter burst: suppression lifted
|
||||||
|
// + full small-resolve readbacks for ~1.5 s so the card-build's inputs are
|
||||||
|
// real (a logged control run showed the composite CPU copies land exactly
|
||||||
|
// in this window; every post-hoc window/bound missed them).
|
||||||
|
extern "C" REX_FUNC(sub_824FD4B0) {
|
||||||
|
if (skate3::native_render::Enabled()) {
|
||||||
|
skate3::native_scene::OnPhotoGrabRequest();
|
||||||
|
}
|
||||||
|
__imp__sub_824FD4B0(ctx, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
// rw::movie::MovieDecoder::Decode(int, VideoRenderable**,
|
// rw::movie::MovieDecoder::Decode(int, VideoRenderable**,
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,15 @@ REXCVAR_DEFINE_BOOL(
|
||||||
"mid-window (mode toggle, unexpected yield), the window watchdog flips "
|
"mid-window (mode toggle, unexpected yield), the window watchdog flips "
|
||||||
"to the emulated fallback by itself.")
|
"to the emulated fallback by itself.")
|
||||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
REXCVAR_DEFINE_BOOL(
|
||||||
|
skate3_native_render_scene_photo_display_yield, false, "Skate 3",
|
||||||
|
"Yield the photo DISPLAY-CARD screen to the emulated output while the "
|
||||||
|
"framed card is up. OFF (default) since the shutter burst: the game "
|
||||||
|
"CPU-composes the framed card into the card texture at the grab (the "
|
||||||
|
"burst makes its inputs real), and the native 2D replay samples that "
|
||||||
|
"same texture; the display screen renders fully native. Turn ON as a "
|
||||||
|
"safety hatch if the display screen misrenders natively.")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_BOOL(
|
REXCVAR_DEFINE_BOOL(
|
||||||
skate3_native_render_scene_photo_native, true, "Skate 3",
|
skate3_native_render_scene_photo_native, true, "Skate 3",
|
||||||
"Render the photo-mission photo editor NATIVELY, applying the game's "
|
"Render the photo-mission photo editor NATIVELY, applying the game's "
|
||||||
|
|
@ -3016,6 +3025,53 @@ std::atomic<bool> g_photo_flow_frame{false};
|
||||||
// full story): stamped by Publish2dDraws on the guest thread whenever the
|
// full story): stamped by Publish2dDraws on the guest thread whenever the
|
||||||
// card-signature quad is in the 2D stream; read by YieldForPhotoDisplay.
|
// card-signature quad is in the 2D stream; read by YieldForPhotoDisplay.
|
||||||
std::atomic<int64_t> g_photo_card_seen_ns{-1};
|
std::atomic<int64_t> g_photo_card_seen_ns{-1};
|
||||||
|
|
||||||
|
// Last ScreenshotBackEnd grab REQUEST (PerfClock ns): the game arms a grab
|
||||||
|
// 1-2 frames before it renders the shot + card-composite frame sequence and
|
||||||
|
// CPU-reads the resolves (GrabScreenshot -> OnScreenShot compose). The
|
||||||
|
// shutter burst (UpdatePhotoGrabWindow) keys on this: the ONLY moment the
|
||||||
|
// card-build's inputs can be made real under the native path.
|
||||||
|
std::atomic<int64_t> g_photo_grab_request_ns{-1};
|
||||||
|
|
||||||
|
void OnPhotoGrabRequest() {
|
||||||
|
g_photo_grab_request_ns.store(
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
PerfClock::now().time_since_epoch())
|
||||||
|
.count(),
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GrabScreenshot returned: the grab CPU-read the resolved shot, and the
|
||||||
|
// OnScreenShot card compose that follows (same guest thread, same Update)
|
||||||
|
// only reads memory the burst already copied. The burst closes here:
|
||||||
|
// event-driven, no timers (a short grace in the window logic covers the
|
||||||
|
// aux-target grab some flows issue on the following frame).
|
||||||
|
std::atomic<int64_t> g_photo_grab_done_ns{-1};
|
||||||
|
|
||||||
|
void OnPhotoGrabDone() {
|
||||||
|
g_photo_grab_done_ns.store(
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
PerfClock::now().time_since_epoch())
|
||||||
|
.count(),
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// True while the photo display-card quad is in the live 2D stream during an
|
||||||
|
// armed photo flow; the compose auto-trace trigger
|
||||||
|
// (skate3_native_render.cpp) keys on it.
|
||||||
|
bool PhotoCardVisible() {
|
||||||
|
if (!g_photo_flow_frame.load(std::memory_order_relaxed)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int64_t seen = g_photo_card_seen_ns.load(std::memory_order_relaxed);
|
||||||
|
if (seen < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int64_t now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
PerfClock::now().time_since_epoch())
|
||||||
|
.count();
|
||||||
|
return now_ns - seen < 250'000'000;
|
||||||
|
}
|
||||||
// Defined with the yield helpers below (anonymous namespace); used by the
|
// Defined with the yield helpers below (anonymous namespace); used by the
|
||||||
// BuildFrameScene publish gate (the chain must only run while the editor
|
// BuildFrameScene publish gate (the chain must only run while the editor
|
||||||
// itself is up).
|
// itself is up).
|
||||||
|
|
@ -5152,7 +5208,10 @@ void OnDrawDone(uint8_t* base, uint32_t func, uint32_t r4, uint32_t r5, uint32_t
|
||||||
std::lock_guard<std::mutex> lock(g_record_mutex);
|
std::lock_guard<std::mutex> lock(g_record_mutex);
|
||||||
const bool frame_recorded = (g_frames_seen + 1) % g_record_stride == 0;
|
const bool frame_recorded = (g_frames_seen + 1) % g_record_stride == 0;
|
||||||
const bool all_draws = REXCVAR_GET(skate3_native_render_snapshot_all_draws);
|
const bool all_draws = REXCVAR_GET(skate3_native_render_snapshot_all_draws);
|
||||||
const size_t draw_cap = all_draws ? 200000 : 32768;
|
// 600k: a 360-frame all-draws photo-compose trace is ~470k records (a
|
||||||
|
// 200k cap cuts such a trace around frame 160, before the
|
||||||
|
// late-firing card compose).
|
||||||
|
const size_t draw_cap = all_draws ? 600000 : 32768;
|
||||||
if (frame_recorded && (all_draws || flags2d != 0 || (g_record_frame % 60) < 2) &&
|
if (frame_recorded && (all_draws || flags2d != 0 || (g_record_frame % 60) < 2) &&
|
||||||
g_recorded_draws.size() < draw_cap) {
|
g_recorded_draws.size() < draw_cap) {
|
||||||
auto rec = std::make_unique<RecordedDraw>();
|
auto rec = std::make_unique<RecordedDraw>();
|
||||||
|
|
@ -14637,14 +14696,32 @@ void UpdatePhotoGrabWindow(uint8_t* base) {
|
||||||
constexpr int32_t kForceReadbackMaxLength = 0x2D0000;
|
constexpr int32_t kForceReadbackMaxLength = 0x2D0000;
|
||||||
static bool s_armed = false;
|
static bool s_armed = false;
|
||||||
static bool s_emulated_armed = false;
|
static bool s_emulated_armed = false;
|
||||||
|
static bool s_burst_armed = false;
|
||||||
static int64_t s_arm_ns = 0;
|
static int64_t s_arm_ns = 0;
|
||||||
static int64_t s_last_run_ns = 0;
|
static int64_t s_last_run_ns = 0;
|
||||||
static int32_t s_readback_saved = 0;
|
static int32_t s_readback_saved = 0;
|
||||||
static bool s_suppress_saved = false;
|
static bool s_suppress_saved = false;
|
||||||
static bool s_halfpx_saved = false;
|
static bool s_halfpx_saved = false;
|
||||||
|
static int32_t s_burst_max_saved = 0;
|
||||||
|
static bool s_burst_suppress_saved = false;
|
||||||
|
static bool s_burst_halfpx_saved = false;
|
||||||
|
static int64_t s_burst_open_ns = 0;
|
||||||
const int64_t now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
const int64_t now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
PerfClock::now().time_since_epoch())
|
PerfClock::now().time_since_epoch())
|
||||||
.count();
|
.count();
|
||||||
|
const auto disarm_burst = [&] {
|
||||||
|
s_burst_armed = false;
|
||||||
|
if (s_burst_max_saved <= 0) {
|
||||||
|
REXCVAR_SET(native_render_force_resolve_readback_max_length,
|
||||||
|
s_burst_max_saved);
|
||||||
|
}
|
||||||
|
if (s_burst_suppress_saved) {
|
||||||
|
REXCVAR_SET(native_render_suppress_emulated_draws, true);
|
||||||
|
}
|
||||||
|
if (!s_burst_halfpx_saved) {
|
||||||
|
REXCVAR_SET(readback_resolve_half_pixel_offset, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
// This function only runs while the native renderer is producing frames
|
// This function only runs while the native renderer is producing frames
|
||||||
// (RenderScene). A gap means the native path was toggled off or yielding
|
// (RenderScene). A gap means the native path was toggled off or yielding
|
||||||
// - grab writes legitimately paused, so give the watchdog a fresh grace
|
// - grab writes legitimately paused, so give the watchdog a fresh grace
|
||||||
|
|
@ -14687,6 +14764,32 @@ void UpdatePhotoGrabWindow(uint8_t* base) {
|
||||||
}
|
}
|
||||||
s_emulated_armed = true;
|
s_emulated_armed = true;
|
||||||
};
|
};
|
||||||
|
// Shader compilation goes SYNCHRONOUS for the whole photo flow; the
|
||||||
|
// menus lesson applied here: with async_shader_compilation on, the d3d12
|
||||||
|
// command processor SKIPS any draw whose pipeline is still compiling, and
|
||||||
|
// the photo display screen's framed-card compose is a ONE-SHOT pass.
|
||||||
|
// Its shaders have been suppressed-cold in every native session (and the
|
||||||
|
// pipeline caches were invalidated by SDK rebuilds), so the compose's
|
||||||
|
// draws were skipped at the exact moment they finally ran, which is why
|
||||||
|
// the card stayed un-framed EVEN IN EMULATED rendering.
|
||||||
|
// One-time compile stalls during a photo UI moment are invisible.
|
||||||
|
static bool s_async_forced = false;
|
||||||
|
static bool s_async_saved = false;
|
||||||
|
if (want && !s_async_forced) {
|
||||||
|
s_async_saved = REXCVAR_GET(async_shader_compilation);
|
||||||
|
if (s_async_saved) {
|
||||||
|
REXCVAR_SET(async_shader_compilation, false);
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: photo flow - shader compilation synchronous (the "
|
||||||
|
"one-shot card compose can't skip still-compiling draws)");
|
||||||
|
}
|
||||||
|
s_async_forced = true;
|
||||||
|
} else if (!want && s_async_forced) {
|
||||||
|
if (s_async_saved) {
|
||||||
|
REXCVAR_SET(async_shader_compilation, true);
|
||||||
|
}
|
||||||
|
s_async_forced = false;
|
||||||
|
}
|
||||||
if (want && !s_armed) {
|
if (want && !s_armed) {
|
||||||
s_armed = true;
|
s_armed = true;
|
||||||
s_arm_ns = now_ns;
|
s_arm_ns = now_ns;
|
||||||
|
|
@ -14702,9 +14805,8 @@ void UpdatePhotoGrabWindow(uint8_t* base) {
|
||||||
g_photo_grab_native_armed.store(true, std::memory_order_relaxed);
|
g_photo_grab_native_armed.store(true, std::memory_order_relaxed);
|
||||||
REXLOG_INFO(
|
REXLOG_INFO(
|
||||||
"native-scene: photo flow - NATIVE grab armed (suppression stays "
|
"native-scene: photo flow - NATIVE grab armed (suppression stays "
|
||||||
"on; the native output is downsampled + CPU-tiled into the guest "
|
"on; native output CPU-tiled into the guest screenshot target; "
|
||||||
"screenshot target each frame; the display-card screen yields to "
|
"display card yields to emulated; shutter burst on grab request)");
|
||||||
"the emulated output)");
|
|
||||||
} else {
|
} else {
|
||||||
arm_emulated();
|
arm_emulated();
|
||||||
REXLOG_INFO(
|
REXLOG_INFO(
|
||||||
|
|
@ -14715,6 +14817,9 @@ void UpdatePhotoGrabWindow(uint8_t* base) {
|
||||||
}
|
}
|
||||||
} else if (!want && s_armed) {
|
} else if (!want && s_armed) {
|
||||||
s_armed = false;
|
s_armed = false;
|
||||||
|
if (s_burst_armed) {
|
||||||
|
disarm_burst();
|
||||||
|
}
|
||||||
if (g_photo_grab_native_armed.exchange(false, std::memory_order_relaxed)) {
|
if (g_photo_grab_native_armed.exchange(false, std::memory_order_relaxed)) {
|
||||||
REXLOG_INFO(
|
REXLOG_INFO(
|
||||||
"native-scene: photo flow ended - native grab wrote {} frames "
|
"native-scene: photo flow ended - native grab wrote {} frames "
|
||||||
|
|
@ -14755,6 +14860,9 @@ void UpdatePhotoGrabWindow(uint8_t* base) {
|
||||||
const int64_t basis = std::max(std::max(last_write, s_arm_ns), yield_end);
|
const int64_t basis = std::max(std::max(last_write, s_arm_ns), yield_end);
|
||||||
if (now_ns - basis > 700'000'000) {
|
if (now_ns - basis > 700'000'000) {
|
||||||
g_photo_grab_native_armed.store(false, std::memory_order_relaxed);
|
g_photo_grab_native_armed.store(false, std::memory_order_relaxed);
|
||||||
|
if (s_burst_armed) {
|
||||||
|
disarm_burst();
|
||||||
|
}
|
||||||
arm_emulated();
|
arm_emulated();
|
||||||
REXLOG_WARN(
|
REXLOG_WARN(
|
||||||
"native-scene: photo flow - native grab produced no frames for "
|
"native-scene: photo flow - native grab produced no frames for "
|
||||||
|
|
@ -14762,6 +14870,67 @@ void UpdatePhotoGrabWindow(uint8_t* base) {
|
||||||
s_suppress_saved ? " + suppression lifted" : "");
|
s_suppress_saved ? " + suppression lifted" : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// SHUTTER BURST: the game builds the framed card as a dedicated frame
|
||||||
|
// sequence at the grab - request (hooked: OnPhotoGrabRequest) -> render
|
||||||
|
// the shot + card-composite passes -> resolve -> GrabScreenshot/
|
||||||
|
// OnScreenShot CPU-read the resolves and CPU-compose the card texture.
|
||||||
|
// Under the native path those frames are suppressed and nothing lands in
|
||||||
|
// CPU memory, so the compose blends zeros = the un-framed card (proven:
|
||||||
|
// the control run's composite CPU copies all land inside this window;
|
||||||
|
// every post-hoc timing window missed it because the CONSUMER runs
|
||||||
|
// immediately at the shutter). For ~1.5 s from the request: suppression
|
||||||
|
// fully lifted + kFull readbacks up to 0x2D0000 (the full composite
|
||||||
|
// class; the handful of drains sits inside the shutter freeze the game
|
||||||
|
// already has). The emulated 0x04911000 resolve may overwrite the
|
||||||
|
// natively-written photo during the burst, same frozen scene, emulated
|
||||||
|
// 3x3 quality, the pre-native-grab standard, acceptable.
|
||||||
|
if (s_armed && !s_emulated_armed &&
|
||||||
|
g_photo_grab_native_armed.load(std::memory_order_relaxed)) {
|
||||||
|
const int64_t req = g_photo_grab_request_ns.load(std::memory_order_relaxed);
|
||||||
|
// EVENT-DRIVEN close: GrabScreenshot COMPLETING (post-call stamp,
|
||||||
|
// OnPhotoGrabDone) means every remaining card-build step, the
|
||||||
|
// OnScreenShot compose included, is CPU-side work on memory the burst
|
||||||
|
// already copied. The burst therefore spans exactly request ->
|
||||||
|
// grab-done: the 1-3 shot frames, the physical minimum, all inside the
|
||||||
|
// game's own shutter freeze, instead of a timer running into the
|
||||||
|
// display fly-in (the user-visible lag spike). A 150 ms grace after
|
||||||
|
// grab-done covers the aux-target grab some flows issue on the
|
||||||
|
// following frame; the 1.5 s cap remains only as the fallback for a
|
||||||
|
// request that never grabs.
|
||||||
|
const int64_t done_ns = g_photo_grab_done_ns.load(std::memory_order_relaxed);
|
||||||
|
const bool grab_done = done_ns > req && now_ns - done_ns > 150'000'000;
|
||||||
|
const bool burst_want =
|
||||||
|
req >= 0 && now_ns - req < 1'500'000'000 && !grab_done;
|
||||||
|
if (burst_want && !s_burst_armed) {
|
||||||
|
s_burst_armed = true;
|
||||||
|
s_burst_max_saved =
|
||||||
|
REXCVAR_GET(native_render_force_resolve_readback_max_length);
|
||||||
|
if (s_burst_max_saved <= 0) {
|
||||||
|
REXCVAR_SET(native_render_force_resolve_readback_max_length, 0x2D0000);
|
||||||
|
}
|
||||||
|
s_burst_suppress_saved = REXCVAR_GET(native_render_suppress_emulated_draws);
|
||||||
|
if (s_burst_suppress_saved) {
|
||||||
|
REXCVAR_SET(native_render_suppress_emulated_draws, false);
|
||||||
|
}
|
||||||
|
s_burst_halfpx_saved = REXCVAR_GET(readback_resolve_half_pixel_offset);
|
||||||
|
if (!s_burst_halfpx_saved) {
|
||||||
|
REXCVAR_SET(readback_resolve_half_pixel_offset, true);
|
||||||
|
}
|
||||||
|
s_burst_open_ns = now_ns;
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: photo grab REQUEST - shutter burst OPEN "
|
||||||
|
"(suppression lifted + <=0x2D0000 kFull readbacks; the shot + "
|
||||||
|
"card-composite frames render and land in guest memory; closes "
|
||||||
|
"when the grab completes)");
|
||||||
|
} else if (!burst_want && s_burst_armed) {
|
||||||
|
disarm_burst();
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: shutter burst closed after {} ms ({}) - "
|
||||||
|
"suppression + readback restored",
|
||||||
|
(now_ns - s_burst_open_ns) / 1'000'000,
|
||||||
|
grab_done ? "grab completed" : "1.5 s cap");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Photo display-card screen: yield to the emulated output while the framed
|
// Photo display-card screen: yield to the emulated output while the framed
|
||||||
|
|
@ -14783,6 +14952,27 @@ bool YieldForPhotoDisplay() {
|
||||||
static bool s_preroll = false;
|
static bool s_preroll = false;
|
||||||
static bool s_suppress_saved = false;
|
static bool s_suppress_saved = false;
|
||||||
static int64_t s_preroll_ns = 0;
|
static int64_t s_preroll_ns = 0;
|
||||||
|
// Default OFF since the shutter burst was added: the framed card
|
||||||
|
// is CPU-composed into the card texture at the grab, and the native 2D
|
||||||
|
// replay samples that same texture; the display screen renders fully
|
||||||
|
// native (card + blurred backdrop + buttons). The yield remains as a
|
||||||
|
// safety hatch for display-screen regressions.
|
||||||
|
if (!REXCVAR_GET(skate3_native_render_scene_photo_display_yield)) {
|
||||||
|
if (s_preroll) {
|
||||||
|
s_preroll = false;
|
||||||
|
if (s_suppress_saved) {
|
||||||
|
REXCVAR_SET(native_render_suppress_emulated_draws, true);
|
||||||
|
}
|
||||||
|
if (g_photo_display_yielding.exchange(false, std::memory_order_relaxed)) {
|
||||||
|
g_photo_display_yield_end_ns.store(
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
PerfClock::now().time_since_epoch())
|
||||||
|
.count(),
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const int64_t now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
const int64_t now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
PerfClock::now().time_since_epoch())
|
PerfClock::now().time_since_epoch())
|
||||||
.count();
|
.count();
|
||||||
|
|
|
||||||
|
|
@ -406,6 +406,15 @@ void OnPhotoReplayUpdate();
|
||||||
// forced-readback window for the frames that render and CPU-grab the
|
// forced-readback window for the frames that render and CPU-grab the
|
||||||
// screenshot target.
|
// screenshot target.
|
||||||
void OnTakePhoto();
|
void OnTakePhoto();
|
||||||
|
// ScreenshotBackEnd grab REQUEST (fires 1-2 frames BEFORE the game renders
|
||||||
|
// the shot + card-composite frame sequence): arms the shutter burst.
|
||||||
|
void OnPhotoGrabRequest();
|
||||||
|
// GrabScreenshot COMPLETED (post-call): every card-build read from here on
|
||||||
|
// is CPU-side of already-copied memory, closes the shutter burst.
|
||||||
|
void OnPhotoGrabDone();
|
||||||
|
// True while the photo display-card quad is in the live 2D stream during an
|
||||||
|
// armed photo flow; the compose auto-trace trigger keys on it.
|
||||||
|
bool PhotoCardVisible();
|
||||||
void OnMovieDecode();
|
void OnMovieDecode();
|
||||||
void OnMovieFrame(uint8_t* base, uint32_t renderer);
|
void OnMovieFrame(uint8_t* base, uint32_t renderer);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue