mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
The renderer gives each frame 131,072 shader-visible descriptors past a persistent 120,000, in heaps of 333,333. Dense custom maps (the Los Santos City sublevels, San Andreas) draw more than that in a frame, and the renderer wrote past the end of the heap: shaders read garbage descriptors, which faulted or hung the GPU (page faults at nonsense addresses, in draws, texture fetches and ray tracing alike). ReSkate now patches the device setup at boot so each heap is D3D12's 1,000,000 and a frame may use 524,288; heaps are 644,288 descriptors. LS-B2, LS-B3 and San Andreas (210,000 a frame at its peak) now hold. Also in this change: - Memory: perf memory owners/heap/gpu/report, a ledger of who commits the game's RAM; the render-resource pool no longer allocates its 115 MB twice; the EadpErrorsData arena is capped at 32 MiB (EA's error reporting is blocked anyway). - Crash reports: tagged with the map, destination, graphics card and driver version (protocol v3), so Sentry can group GPU hangs by map and card. - GPU diagnostics (--gpu-diagnostics): a fuller DRED report, the device-removal and video memory watches, a large-draw watch, a record of which game code made and freed each GPU heap and resource, checks of every GPU address bound, and a meter of each descriptor heap's use. - World pools: peak use of entity pages, render instances and spatial blocks in the log. - dingosdk_mod_mesh_check: checks every MeshSet a mod ships against its geometry; the MeshSet reader now knows static meshes' layout.
124 lines
6.3 KiB
C++
124 lines
6.3 KiB
C++
#pragma once
|
|
#include "Engine/Core/Log/types.h"
|
|
#include "Engine/Core/Debug/crash_report.h"
|
|
#include "Engine/Game/World/client_state.h"
|
|
#include <cstdint>
|
|
#include <format>
|
|
#include <string>
|
|
|
|
namespace dingosdk::level_logging {
|
|
using logging::Level;
|
|
using Emit = void (*)(Level, std::string_view);
|
|
// Extra words for a destination that is taking too long or never arrived:
|
|
// which mod a custom level came from, and what was known to be wrong with it.
|
|
using Hint = std::string (*)(std::string_view destination);
|
|
inline bool busy(unsigned state) noexcept {
|
|
return state < 24 && state != 2 && state != 13 && state != 21;
|
|
}
|
|
struct Sample {
|
|
std::uintptr_t client{};
|
|
std::uint64_t at{};
|
|
unsigned state{26}, previous{26}, game_type{}, pending{}, descriptions{};
|
|
bool queues_known{};
|
|
std::string destination;
|
|
};
|
|
|
|
// Only SDK-owned data is retained between callbacks. No native pointer is
|
|
// dereferenced later to reconstruct a past transition or its destination.
|
|
struct Progress {
|
|
std::uintptr_t client{};
|
|
unsigned state{26}, stage_lines{}, slow_handlers{}, debug_lines{}, destination_lines{}, wait_warnings{}, slowest_state{26};
|
|
std::uint64_t started{}, entered{}, sequence{}, slowest_ms{};
|
|
bool running{}, seen{}, warned{};
|
|
std::string destination;
|
|
Hint hint{};
|
|
|
|
void observe(const Sample& s, bool exact_transition, Emit emit) {
|
|
if (s.state > 26 || s.previous > 26) return;
|
|
if (client != s.client) {
|
|
const auto last_sequence = sequence;
|
|
const auto last_hint = hint;
|
|
*this = {};
|
|
sequence = last_sequence;
|
|
hint = last_hint;
|
|
client = s.client;
|
|
}
|
|
if (busy(s.state) && !running) {
|
|
running = true;
|
|
started = s.at;
|
|
stage_lines = slow_handlers = debug_lines = destination_lines = wait_warnings = 0;
|
|
slowest_ms = 0; slowest_state = 26;
|
|
seen = false;
|
|
destination.clear();
|
|
++sequence;
|
|
}
|
|
const bool changed = !seen || state != s.state;
|
|
const auto previous_ms = seen && s.at >= entered ? s.at - entered : 0;
|
|
const auto elapsed = running && s.at >= started ? s.at - started : 0;
|
|
if (changed) {
|
|
if (running && previous_ms > slowest_ms) { slowest_ms = previous_ms; slowest_state = state; }
|
|
entered = s.at; warned = false;
|
|
}
|
|
// The previous world's descriptor can survive unloading/server startup.
|
|
// Name the destination only once the new load reaches its asset stage.
|
|
if (((s.state >= 8 && s.state <= 13) || (s.state >= 16 && s.state <= 21)) &&
|
|
!s.destination.empty() && s.destination != destination) {
|
|
destination = s.destination;
|
|
// (A crash report from here on is tagged with this level: crash_report.h.)
|
|
dingosdk::crash_report::set_destination(std::wstring(destination.begin(), destination.end()));
|
|
if (destination_lines++ < 4)
|
|
emit(Level::info, std::format("Level destination: {}.", destination));
|
|
}
|
|
const auto queues = s.queues_known
|
|
? std::format("{} queued transitions, {} queued descriptions", s.pending, s.descriptions)
|
|
: std::string("queue counts unavailable");
|
|
if ((exact_transition || changed) && debug_lines++ < 128) {
|
|
emit(Level::debug, std::format("{}: {} ({}) -> {} ({}); {:.3f}s in previous observed state; {}; game type {}.",
|
|
exact_transition ? "Native level transition" : "Level state sample",
|
|
client_state_name(exact_transition ? s.previous : state), exact_transition ? s.previous : state,
|
|
client_state_name(s.state), s.state, static_cast<double>(previous_ms) / 1000.0,
|
|
queues, s.game_type));
|
|
}
|
|
const auto severity = s.state == 2 ? Level::warning :
|
|
(s.state == 13 || s.state == 21) ? Level::success : Level::info;
|
|
if (changed && s.state != 26 && stage_lines++ < 128) {
|
|
emit(severity, running ? std::format("{} [{}] (load {}, {:.2f}s elapsed, {:.3f}s in previous observed state; {}).",
|
|
client_state_name(s.state), s.state, sequence, static_cast<double>(elapsed) / 1000.0,
|
|
static_cast<double>(previous_ms) / 1000.0, queues) :
|
|
std::format("{} [{}] ({}).", client_state_name(s.state), s.state, queues));
|
|
}
|
|
// Even a noisy/retrying state machine must leave a completion record
|
|
// once it reaches a terminal state, after the bounded stage detail.
|
|
if (running && !busy(s.state)) {
|
|
const bool active = s.state == 13 || s.state == 21;
|
|
const auto where = destination.empty() ? std::string{}
|
|
: "; " + destination + (!active && hint ? hint(destination) : std::string{});
|
|
emit(severity, std::format("Load {} {} after {:.2f}s{}; longest observed stage: {} ({:.2f}s).",
|
|
sequence, active ? "content active" : "ended before content became active",
|
|
static_cast<double>(elapsed) / 1000.0, where,
|
|
client_state_name(slowest_state), static_cast<double>(slowest_ms) / 1000.0));
|
|
}
|
|
state = s.state;
|
|
seen = true;
|
|
if (!busy(state)) running = false;
|
|
// One warning per stalled state, at most three per load. Polling keeps
|
|
// a long wait visible without producing per-frame or per-asset output.
|
|
if (running && !warned && s.at >= entered && s.at - entered >= 30000) {
|
|
warned = true;
|
|
if (wait_warnings++ < 3)
|
|
emit(Level::warning, std::format("Level load {} still waiting: {} for {:.1f}s ({:.1f}s total); {}{}.",
|
|
sequence, client_state_name(state), static_cast<double>(s.at - entered) / 1000.0,
|
|
static_cast<double>(elapsed) / 1000.0, queues,
|
|
destination.empty() ? std::string{}
|
|
: "; " + destination + (hint ? hint(destination) : std::string{})));
|
|
}
|
|
}
|
|
void returned(unsigned next, std::uint64_t duration, Emit emit) {
|
|
if (next > 26 || duration < 1000 || (slow_handlers & (1u << next))) return;
|
|
slow_handlers |= 1u << next;
|
|
emit(Level::warning, std::format("Native {} transition handler took {:.2f}s.",
|
|
client_state_name(next), static_cast<double>(duration) / 1000.0));
|
|
}
|
|
};
|
|
}
|