capture: pair deferred rigid clone worlds by identity instead of submission order
This commit is contained in:
parent
186c7de6f4
commit
89360e53fc
2 changed files with 100 additions and 0 deletions
|
|
@ -4172,6 +4172,14 @@ uint32_t CaptureDynamicState(uint8_t* base, uint32_t ctx, bool world_path,
|
||||||
!BankRigidWorld(base, bank, item.world)) {
|
!BankRigidWorld(base, bank, item.world)) {
|
||||||
item.pending = true;
|
item.pending = true;
|
||||||
g_rigid_pending.fetch_add(1, std::memory_order_relaxed);
|
g_rigid_pending.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
} else if (item.ctx != 0) {
|
||||||
|
if (g_rigid_world_cache.size() > 32768) {
|
||||||
|
g_rigid_world_cache.clear(); // map-change growth backstop
|
||||||
|
}
|
||||||
|
RigidWorldCache& rc = g_rigid_world_cache[item.ctx];
|
||||||
|
rc.mesh = item.mesh;
|
||||||
|
std::memcpy(rc.world, item.world, sizeof(rc.world));
|
||||||
|
rc.frame = g_guest_frame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5540,6 +5548,47 @@ void OnDrawDone(uint8_t* base, uint32_t func, uint32_t r4, uint32_t r5, uint32_t
|
||||||
}
|
}
|
||||||
caster_refresh = true;
|
caster_refresh = true;
|
||||||
}
|
}
|
||||||
|
// Rigid clone disambiguation: same-mesh piece clones share these buffers,
|
||||||
|
// and under batched dispatch (park-editor venues: every capture defers,
|
||||||
|
// 100+ pending rigids per frame) the FIFO capture order is NOT the draw
|
||||||
|
// order; pairing a draw with the wrong clone swaps piece worlds frame to
|
||||||
|
// frame (pieces visibly shuffle and flip 180 degrees). Prefer the pending
|
||||||
|
// clone whose LAST PUBLISHED world sits nearest this draw's world; FIFO
|
||||||
|
// stays the fallback for first-sight clones with no history. Movables stay
|
||||||
|
// matchable: they drift well under the 0.5 m bound per frame and the cache
|
||||||
|
// re-stamps on every resolved draw.
|
||||||
|
if (!caster_refresh && oldest->second < g_frame_dynitems.size() &&
|
||||||
|
!g_frame_dynitems[oldest->second].skinned) {
|
||||||
|
float w[16];
|
||||||
|
if (!BankIsAuxPerspective(base, bank) && BankRigidWorld(base, bank, w)) {
|
||||||
|
float best_d2 = 0.25f;
|
||||||
|
auto best = range.second;
|
||||||
|
for (auto it = range.first; it != range.second; ++it) {
|
||||||
|
if (it->second >= g_frame_dynitems.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const DrawItem& c = g_frame_dynitems[it->second];
|
||||||
|
if (!c.pending || c.skinned || c.ctx == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto cit = g_rigid_world_cache.find(c.ctx);
|
||||||
|
if (cit == g_rigid_world_cache.end() || cit->second.mesh != c.mesh) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const float dx = cit->second.world[12] - w[12];
|
||||||
|
const float dy = cit->second.world[13] - w[13];
|
||||||
|
const float dz = cit->second.world[14] - w[14];
|
||||||
|
const float d2 = dx * dx + dy * dy + dz * dz;
|
||||||
|
if (d2 < best_d2) {
|
||||||
|
best_d2 = d2;
|
||||||
|
best = it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best != range.second) {
|
||||||
|
oldest = best;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
DrawItem& d = g_frame_dynitems[oldest->second];
|
DrawItem& d = g_frame_dynitems[oldest->second];
|
||||||
if (caster_refresh) {
|
if (caster_refresh) {
|
||||||
// Refresh of a caster-sourced capture from a later (ideally main-pass)
|
// Refresh of a caster-sourced capture from a later (ideally main-pass)
|
||||||
|
|
@ -5598,6 +5647,12 @@ void OnDrawDone(uint8_t* base, uint32_t func, uint32_t r4, uint32_t r5, uint32_t
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
d.caster_bank = false;
|
d.caster_bank = false;
|
||||||
|
if (d.ctx != 0) {
|
||||||
|
RigidWorldCache& rc = g_rigid_world_cache[d.ctx];
|
||||||
|
rc.mesh = d.mesh;
|
||||||
|
std::memcpy(rc.world, d.world, sizeof(rc.world));
|
||||||
|
rc.frame = g_guest_frame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d.pending = false;
|
d.pending = false;
|
||||||
d.dbg_src = 2;
|
d.dbg_src = 2;
|
||||||
|
|
@ -8293,6 +8348,9 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
|
||||||
// refused capture re-publishes with the instance's own last palette
|
// refused capture re-publishes with the instance's own last palette
|
||||||
// (see g_bones_cache_ctx).
|
// (see g_bones_cache_ctx).
|
||||||
std::unordered_map<uint32_t, const DrawItem*> pending_skinned_by_ctx;
|
std::unordered_map<uint32_t, const DrawItem*> pending_skinned_by_ctx;
|
||||||
|
// Per-instance rigid candidates whose post-draw world fixup never landed
|
||||||
|
// this frame; rescued with their cached world (see g_rigid_world_cache).
|
||||||
|
std::unordered_map<uint32_t, const DrawItem*> pending_rigid_by_ctx;
|
||||||
const auto total_indices = [](const DrawItem& d) {
|
const auto total_indices = [](const DrawItem& d) {
|
||||||
uint64_t n = 0;
|
uint64_t n = 0;
|
||||||
for (const DrawEntry& e : d.draws) n += e.index_count;
|
for (const DrawEntry& e : d.draws) n += e.index_count;
|
||||||
|
|
@ -8355,6 +8413,8 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
|
||||||
pending_ropa_by_mesh.try_emplace(cand.mesh, &cand);
|
pending_ropa_by_mesh.try_emplace(cand.mesh, &cand);
|
||||||
} else if (cand.skinned && cand.ctx != 0 && cand.char_family != 0) {
|
} else if (cand.skinned && cand.ctx != 0 && cand.char_family != 0) {
|
||||||
pending_skinned_by_ctx.try_emplace(cand.ctx, &cand);
|
pending_skinned_by_ctx.try_emplace(cand.ctx, &cand);
|
||||||
|
} else if (!cand.skinned && !cand.cloth_quads && cand.ctx != 0) {
|
||||||
|
pending_rigid_by_ctx.try_emplace(cand.ctx, &cand);
|
||||||
}
|
}
|
||||||
(cand.skinned ? g_skinned_skipped : g_rigid_dropped)
|
(cand.skinned ? g_skinned_skipped : g_rigid_dropped)
|
||||||
.fetch_add(1, std::memory_order_relaxed);
|
.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
@ -8636,6 +8696,29 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
|
||||||
dyn_slot.try_emplace(ctxk, scene.items.size() - 1);
|
dyn_slot.try_emplace(ctxk, scene.items.size() - 1);
|
||||||
g_lw_ctx_rescued.fetch_add(1, std::memory_order_relaxed);
|
g_lw_ctx_rescued.fetch_add(1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
// Rigid clone rescue: a piece whose world-bearing draw was missed this
|
||||||
|
// frame (culled, or claimed by another clone of the mesh) re-publishes
|
||||||
|
// with its own last published world instead of vanishing for a frame.
|
||||||
|
// Statics never move, and movables at most drift one frame behind; the
|
||||||
|
// mesh check keeps a reused ctx address from wearing a stale placement.
|
||||||
|
for (const auto& [ctxk, cand] : pending_rigid_by_ctx) {
|
||||||
|
if (dyn_slot.find(ctxk) != dyn_slot.end()) {
|
||||||
|
continue; // this instance published live
|
||||||
|
}
|
||||||
|
const auto cit = g_rigid_world_cache.find(ctxk);
|
||||||
|
if (cit == g_rigid_world_cache.end() || cit->second.mesh != cand->mesh ||
|
||||||
|
g_guest_frame - cit->second.frame > 600) {
|
||||||
|
continue; // no trusted placement; a dropped frame beats an origin ghost
|
||||||
|
}
|
||||||
|
scene.items.push_back(*cand);
|
||||||
|
DrawItem& rescued = scene.items.back();
|
||||||
|
std::memcpy(rescued.world, cit->second.world, sizeof(rescued.world));
|
||||||
|
rescued.pending = false;
|
||||||
|
rescued.dbg_src = 12;
|
||||||
|
++pub_count[rescued.mesh];
|
||||||
|
dyn_slot.try_emplace(ctxk, scene.items.size() - 1);
|
||||||
|
g_rigid_rescued.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
// Refused ropa captures re-publish last frame's resolved state: mode,
|
// Refused ropa captures re-publish last frame's resolved state: mode,
|
||||||
// world AND palette together (mixing frames' interpretations is the
|
// world AND palette together (mixing frames' interpretations is the
|
||||||
// mangled-ribbon bug; see g_ropa_state_cache).
|
// mangled-ribbon bug; see g_ropa_state_cache).
|
||||||
|
|
|
||||||
|
|
@ -819,6 +819,23 @@ inline std::atomic<uint64_t> g_skinned_skipped{0};
|
||||||
// draws), and those still pending at frame end (dropped).
|
// draws), and those still pending at frame end (dropped).
|
||||||
inline std::atomic<uint64_t> g_rigid_pending{0};
|
inline std::atomic<uint64_t> g_rigid_pending{0};
|
||||||
inline std::atomic<uint64_t> g_rigid_dropped{0};
|
inline std::atomic<uint64_t> g_rigid_dropped{0};
|
||||||
|
// Pending rigid clones re-published with their cached world at merge time
|
||||||
|
// (see g_rigid_world_cache).
|
||||||
|
inline std::atomic<uint64_t> g_rigid_rescued{0};
|
||||||
|
// Last published world per rigid dynamic context. Guest render thread only.
|
||||||
|
// Same-mesh piece clones (park-editor venues submit dozens) share (ib,vb),
|
||||||
|
// and their hook-time captures defer to the post-draw fixup whenever the
|
||||||
|
// venue batches dispatch (own_draw_last false); FIFO pairing there scrambles
|
||||||
|
// clone worlds whenever capture order and draw order diverge - pieces
|
||||||
|
// visibly swap placements and flip 180 degrees frame to frame. The fixup
|
||||||
|
// pairs each draw to the pending clone whose cached world matches instead,
|
||||||
|
// and clones whose draw never landed re-publish their cached world at merge.
|
||||||
|
struct RigidWorldCache {
|
||||||
|
uint32_t mesh = 0;
|
||||||
|
float world[16] = {};
|
||||||
|
uint64_t frame = 0;
|
||||||
|
};
|
||||||
|
inline std::unordered_map<uint32_t, RigidWorldCache> g_rigid_world_cache;
|
||||||
// Model-space props in the world sort lists, excluded from the identity
|
// Model-space props in the world sort lists, excluded from the identity
|
||||||
// world path (their placed copies come from the kind-2 capture).
|
// world path (their placed copies come from the kind-2 capture).
|
||||||
inline std::atomic<uint64_t> g_world_props{0};
|
inline std::atomic<uint64_t> g_world_props{0};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue