app: organize the F12 debug menu into sections, add a max-quality photo mode

This commit is contained in:
mchughalex 2026-07-19 23:50:32 +01:00
parent cb14799e47
commit 84f3f94ceb
2 changed files with 519 additions and 271 deletions

View file

@ -45,6 +45,12 @@ REXCVAR_DECLARE(bool, skate3_native_render_scene_tex_revalidate);
REXCVAR_DECLARE(bool, skate3_native_render_scene_mesh_revalidate);
REXCVAR_DECLARE(bool, skate3_native_render_scene_tex_mips);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_debug);
// Image quality (hot; skate3_native_scene.cpp).
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_msaa);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shadow_tile);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shadow_static_size);
REXCVAR_DECLARE(bool, skate3_native_render_scene_ssao_full_res);
REXCVAR_DECLARE(bool, skate3_native_render_scene_hdr_packed);
// HDR post-effect stack (hot; skate3_native_scene.cpp).
REXCVAR_DECLARE(bool, skate3_native_render_scene_hdr);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_hdr_debug);
@ -97,6 +103,10 @@ REXCVAR_DECLARE(double, skate3_native_render_scene_freecam_speed);
REXCVAR_DECLARE(double, skate3_native_render_scene_freecam_look_speed);
REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam_capture_input);
REXCVAR_DECLARE(bool, skate3_native_render_scene_ssr);
// Draw distance (hot; skate3_draw_distance.cpp).
REXCVAR_DECLARE(double, skate3_draw_distance_scale);
REXCVAR_DECLARE(double, skate3_lod_distance_scale);
REXCVAR_DECLARE(double, skate3_draw_distance_stream_probe);
namespace skate3 {
namespace {
@ -124,6 +134,145 @@ double CvarSlider(const char* label, double value, float lo, float hi,
return value;
}
// Combo over a fixed value list (for quality steps that are not a
// continuum: sample counts, map sizes). Returns the possibly-changed value;
// out-of-list current values snap to the nearest entry's label without
// writing back until the user picks one.
int32_t ValueCombo(const char* label, int32_t value, const int32_t* values,
const char* const* labels, int count,
const char* help = nullptr) {
int index = 0;
for (int i = 1; i < count; ++i) {
const int32_t di = values[i] > value ? values[i] - value : value - values[i];
const int32_t db = values[index] > value ? values[index] - value
: value - values[index];
if (di < db) {
index = i;
}
}
if (ImGui::Combo(label, &index, labels, count)) {
value = values[index];
}
if (help != nullptr && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", help);
}
return value;
}
// ---- Max quality (photo mode) ---------------------------------------------
// One switch that pushes every quality knob to its maximum for
// capture/recording (showcase runs, promo footage, screenshots) and
// restores the previous values when switched off. Session-scoped: nothing
// is persisted. Aesthetic calibrations (bloom/shaft/haze strengths, SSAO
// tuning) keep their values: this maximizes fidelity, not effect strength.
// SSR stays as-is (experimental, image-quality issues) and quad-list
// particles stay off (no sprite textures).
struct MaxQualityState {
bool active = false;
// Saved values, restored on switch-off.
int32_t msaa;
int32_t shadow_tile;
int32_t static_size;
int32_t shafts_steps;
bool ssao_full_res;
bool hdr_packed;
bool hdr;
bool bloom;
bool shafts;
bool ssao;
bool shadows;
bool static_casters;
bool pcss;
double draw_scale;
double lod_scale;
double stream_probe;
};
MaxQualityState s_max_quality;
void SetMaxQuality(bool enable) {
MaxQualityState& s = s_max_quality;
if (enable == s.active) {
return;
}
if (enable) {
s.msaa = REXCVAR_GET(skate3_native_render_scene_msaa);
s.shadow_tile = REXCVAR_GET(skate3_native_render_scene_shadow_tile);
s.static_size = REXCVAR_GET(skate3_native_render_scene_shadow_static_size);
s.shafts_steps = REXCVAR_GET(skate3_native_render_scene_shafts_steps);
s.ssao_full_res = REXCVAR_GET(skate3_native_render_scene_ssao_full_res);
s.hdr_packed = REXCVAR_GET(skate3_native_render_scene_hdr_packed);
s.hdr = REXCVAR_GET(skate3_native_render_scene_hdr);
s.bloom = REXCVAR_GET(skate3_native_render_scene_bloom);
s.shafts = REXCVAR_GET(skate3_native_render_scene_shafts);
s.ssao = REXCVAR_GET(skate3_native_render_scene_ssao);
s.shadows = REXCVAR_GET(skate3_native_render_scene_shadows);
s.static_casters =
REXCVAR_GET(skate3_native_render_scene_shadow_static_casters);
s.pcss = REXCVAR_GET(skate3_native_render_scene_shadow_pcss);
s.draw_scale = REXCVAR_GET(skate3_draw_distance_scale);
s.lod_scale = REXCVAR_GET(skate3_lod_distance_scale);
s.stream_probe = REXCVAR_GET(skate3_draw_distance_stream_probe);
REXCVAR_SET(skate3_native_render_scene_msaa, 8);
REXCVAR_SET(skate3_native_render_scene_shadow_tile, 4096);
REXCVAR_SET(skate3_native_render_scene_shadow_static_size, 8192);
REXCVAR_SET(skate3_native_render_scene_shafts_steps, 64);
REXCVAR_SET(skate3_native_render_scene_ssao_full_res, true);
REXCVAR_SET(skate3_native_render_scene_hdr_packed, false); // RGBA16F
REXCVAR_SET(skate3_native_render_scene_hdr, true);
REXCVAR_SET(skate3_native_render_scene_bloom, true);
REXCVAR_SET(skate3_native_render_scene_shafts, true);
REXCVAR_SET(skate3_native_render_scene_ssao, true);
REXCVAR_SET(skate3_native_render_scene_shadows, true);
REXCVAR_SET(skate3_native_render_scene_shadow_static_casters, true);
REXCVAR_SET(skate3_native_render_scene_shadow_pcss, true);
// The largest steps the settings menu offers (tested territory).
REXCVAR_SET(skate3_draw_distance_scale, 5.0);
REXCVAR_SET(skate3_lod_distance_scale, 5.0);
REXCVAR_SET(skate3_draw_distance_stream_probe, 300.0);
} else {
REXCVAR_SET(skate3_native_render_scene_msaa, s.msaa);
REXCVAR_SET(skate3_native_render_scene_shadow_tile, s.shadow_tile);
REXCVAR_SET(skate3_native_render_scene_shadow_static_size, s.static_size);
REXCVAR_SET(skate3_native_render_scene_shafts_steps, s.shafts_steps);
REXCVAR_SET(skate3_native_render_scene_ssao_full_res, s.ssao_full_res);
REXCVAR_SET(skate3_native_render_scene_hdr_packed, s.hdr_packed);
REXCVAR_SET(skate3_native_render_scene_hdr, s.hdr);
REXCVAR_SET(skate3_native_render_scene_bloom, s.bloom);
REXCVAR_SET(skate3_native_render_scene_shafts, s.shafts);
REXCVAR_SET(skate3_native_render_scene_ssao, s.ssao);
REXCVAR_SET(skate3_native_render_scene_shadows, s.shadows);
REXCVAR_SET(skate3_native_render_scene_shadow_static_casters,
s.static_casters);
REXCVAR_SET(skate3_native_render_scene_shadow_pcss, s.pcss);
REXCVAR_SET(skate3_draw_distance_scale, s.draw_scale);
REXCVAR_SET(skate3_lod_distance_scale, s.lod_scale);
REXCVAR_SET(skate3_draw_distance_stream_probe, s.stream_probe);
}
s.active = enable;
}
void DrawMaxQualityToggle() {
bool on = s_max_quality.active;
if (ImGui::Checkbox("MAX QUALITY (photo mode)", &on)) {
SetMaxQuality(on);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Push every quality setting to its maximum for recording: MSAA 8x,\n"
"4096 shadow tiles, 8192 static sun map, full-res SSAO, RGBA16F\n"
"HDR, max shaft steps, 5x draw/LOD distance, 300 m streaming, all\n"
"quality features on. GPU-heavy; previous values restore when\n"
"unchecked. Applies live (a one-frame pipeline rebuild).");
}
if (s_max_quality.active) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.3f, 1.0f), "ACTIVE");
}
}
// ---- Showcase setup window ------------------------------------------------
// Editor for skate3_native_render_scene_showcase_order: one row per build-up
// layer, reorderable, each optionally joined to the previous row's wipe. The
@ -393,99 +542,13 @@ void DrawShowcaseSetupWindow(bool* p_open) {
ImGui::End();
}
} // namespace
// ---- F12 menu sections ----------------------------------------------------
// One CollapsingHeader per intent group. The groups the menu is actually
// opened for (capture tools, image quality) default open; tuning and
// diagnostics stay collapsed until needed.
void NativeDebugDialog::Show() {
visible_ = true;
SetDrawActive(true);
}
void NativeDebugDialog::Hide() {
if (!visible_) {
return;
}
visible_ = false;
SetDrawActive(false);
}
void NativeDebugDialog::Toggle() {
if (visible_) {
Hide();
} else {
Show();
}
}
void NativeDebugDialog::OnDraw(ImGuiIO& io) {
(void)io;
if (!visible_) {
return;
}
bool open = visible_;
ImGui::SetNextWindowSize(ImVec2(430.0f, 0.0f), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Native Render Debug (F12)", &open, ImGuiWindowFlags_NoCollapse)) {
ImGui::End();
if (!open) {
Hide();
}
return;
}
if (!REXCVAR_GET(skate3_native_render)) {
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.3f, 1.0f),
"skate3_native_render hook layer is OFF (boot-time)");
}
ImGui::SeparatorText("Renderer");
{
const bool v = CvarCheckbox("Native scene renderer (F5)",
REXCVAR_GET(skate3_native_render_scene),
"Full native/emulated switch, same as F5");
if (v != REXCVAR_GET(skate3_native_render_scene)) {
skate3::native_scene::ToggleSceneEnabled();
}
}
REXCVAR_SET(native_render_suppress_emulated_draws,
CvarCheckbox("Suppress emulated draws",
REXCVAR_GET(native_render_suppress_emulated_draws),
"Skip emulated GPU work for framebuffer-sized passes while "
"native output is active (perf). Small-surface passes "
"(lightmap page composition) always run."));
{
int mode = REXCVAR_GET(skate3_native_render_scene_debug);
const char* kModes[] = {"0: normal", "1: clear only", "2: solid colors",
"3: first 20 items", "4: no depth"};
if (ImGui::Combo("debug mode", &mode, kModes, 5)) {
REXCVAR_SET(skate3_native_render_scene_debug, mode);
}
}
ImGui::SeparatorText("World shading");
REXCVAR_SET(skate3_native_render_scene_lightmaps,
CvarCheckbox("Lightmaps", REXCVAR_GET(skate3_native_render_scene_lightmaps),
"Baked lighting atlas sample x2 on world materials"));
REXCVAR_SET(skate3_native_render_scene_macro,
CvarCheckbox("Macro overlay", REXCVAR_GET(skate3_native_render_scene_macro),
"Large-scale grime/crack multiply (ground/wall weathering)"));
REXCVAR_SET(skate3_native_render_scene_decals,
CvarCheckbox("Decal art composite",
REXCVAR_GET(skate3_native_render_scene_decals),
"Graffiti/paint art lerped over environment.decal sections"));
REXCVAR_SET(skate3_native_render_scene_transparents,
CvarCheckbox("Transparent sub-pass",
REXCVAR_GET(skate3_native_render_scene_transparents),
"environment.transparent items (mist sheets, glass, fences)"));
REXCVAR_SET(skate3_native_render_scene_shadows,
CvarCheckbox("Dynamic shadows",
REXCVAR_GET(skate3_native_render_scene_shadows),
"Native CSM: skater/NPC/prop shadows onto the world"));
REXCVAR_SET(skate3_native_render_scene_backface_cull,
CvarCheckbox("Backface cull (game parity)",
REXCVAR_GET(skate3_native_render_scene_backface_cull),
"World env materials cull FRONT like the game's material "
"XMLs; off = legacy cull-none (shows interior faces)"));
ImGui::SeparatorText("Graphics showcase");
void DrawShowcaseCaptureSection() {
DrawMaxQualityToggle();
{
const bool running = REXCVAR_GET(skate3_native_render_scene_showcase);
if (ImGui::Button(running ? "Cancel showcase" : "Start showcase (Home)")) {
@ -503,25 +566,86 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
s_showcase_setup_open = true;
}
}
DrawFreecamControls();
}
ImGui::SeparatorText("HDR post effects (live)");
void DrawImageQualitySection() {
{
static const int32_t kMsaaValues[] = {1, 2, 4, 8};
static const char* const kMsaaLabels[] = {"Off (1x)", "2x", "4x", "8x"};
REXCVAR_SET(skate3_native_render_scene_msaa,
ValueCombo("MSAA", REXCVAR_GET(skate3_native_render_scene_msaa),
kMsaaValues, kMsaaLabels, 4,
"Scene multisampling. Distant thin geometry "
"(railings, wires) shimmers without it. Applies "
"live (pipeline rebuild)."));
}
{
static const int32_t kTileValues[] = {0, 512, 1024, 2048, 4096};
static const char* const kTileLabels[] = {"Auto (render scale)", "512",
"1024", "2048", "4096"};
REXCVAR_SET(
skate3_native_render_scene_shadow_tile,
ValueCombo("shadow tile size",
REXCVAR_GET(skate3_native_render_scene_shadow_tile),
kTileValues, kTileLabels, 5,
"Dynamic-shadow cascade tile resolution. Auto matches the "
"emulated renderer's crispness at the current Resolution "
"Scale; 512 = the softer original-console look."));
}
{
static const int32_t kStaticValues[] = {1024, 2048, 4096, 8192};
static const char* const kStaticLabels[] = {"1024", "2048", "4096",
"8192"};
REXCVAR_SET(
skate3_native_render_scene_shadow_static_size,
ValueCombo("static sun map size",
REXCVAR_GET(skate3_native_render_scene_shadow_static_size),
kStaticValues, kStaticLabels, 4,
"Static sun-shadow map resolution per cascade tile (the "
"map is three tiles)."));
}
REXCVAR_SET(skate3_native_render_scene_ssao_full_res,
CvarCheckbox("Full-res SSAO",
REXCVAR_GET(skate3_native_render_scene_ssao_full_res),
"Evaluate SSAO at full output resolution instead "
"of half. ~4x the AO cost for slightly sharper "
"contact shadows."));
REXCVAR_SET(skate3_native_render_scene_hdr_packed,
CvarCheckbox("Packed HDR format (R11G11B10)",
REXCVAR_GET(skate3_native_render_scene_hdr_packed),
"Halves scene-pass color bandwidth vs RGBA16F at "
"slightly lower precision in very dark "
"gradients."));
}
void DrawLightingPostSection() {
REXCVAR_SET(skate3_native_render_scene_hdr,
CvarCheckbox("HDR intermediate",
REXCVAR_GET(skate3_native_render_scene_hdr),
"Float scene target + single host tonemap, the basis for "
"bloom, shafts and haze. Off = the classic in-material "
"tonemap (parity A/B)."));
{
int dbg = REXCVAR_GET(skate3_native_render_scene_hdr_debug);
const char* kHdrDbg[] = {"0: off", "1: bloom term",
"2: raw pre-tonemap", "3: AO plane",
"4: shaft plane", "5: haze term"};
if (ImGui::Combo("HDR debug view", &dbg, kHdrDbg, 6)) {
REXCVAR_SET(skate3_native_render_scene_hdr_debug, dbg);
}
}
REXCVAR_SET(skate3_native_render_scene_bloom,
CvarCheckbox("Bloom",
REXCVAR_GET(skate3_native_render_scene_bloom),
"Downsample/upsample pyramid driven by pre-tonemap "
"brightness (night lamps, neon, sun glare)."));
REXCVAR_SET(skate3_native_render_scene_bloom_threshold,
CvarSlider("bloom threshold",
REXCVAR_GET(skate3_native_render_scene_bloom_threshold),
0.0f, 1.5f, "%.2f",
"Pre-tonemap onset (1.0 = the tone curve's saturation "
"point). Below ~0.7 the sunlit day frame starts feeding "
"the pyramid and veils in glow."));
REXCVAR_SET(skate3_native_render_scene_bloom_knee,
CvarSlider("bloom knee",
REXCVAR_GET(skate3_native_render_scene_bloom_knee),
0.0f, 0.5f, "%.2f"));
REXCVAR_SET(skate3_native_render_scene_bloom_intensity,
CvarSlider("bloom intensity",
REXCVAR_GET(skate3_native_render_scene_bloom_intensity),
0.0f, 0.3f, "%.3f"));
REXCVAR_SET(skate3_native_render_scene_shafts,
CvarCheckbox("Volumetric sun shafts",
REXCVAR_GET(skate3_native_render_scene_shafts),
@ -563,26 +687,6 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
0.0f, 0.02f, "%.4f",
"How quickly the scattering saturates with distance "
"(0.005 reaches ~63% at 200 units)."));
REXCVAR_SET(skate3_native_render_scene_bloom,
CvarCheckbox("Bloom",
REXCVAR_GET(skate3_native_render_scene_bloom),
"Downsample/upsample pyramid driven by pre-tonemap "
"brightness (night lamps, neon, sun glare)."));
REXCVAR_SET(skate3_native_render_scene_bloom_threshold,
CvarSlider("bloom threshold",
REXCVAR_GET(skate3_native_render_scene_bloom_threshold),
0.0f, 1.5f, "%.2f",
"Pre-tonemap onset (1.0 = the tone curve's saturation "
"point). Below ~0.7 the sunlit day frame starts feeding "
"the pyramid and veils in glow."));
REXCVAR_SET(skate3_native_render_scene_bloom_knee,
CvarSlider("bloom knee",
REXCVAR_GET(skate3_native_render_scene_bloom_knee),
0.0f, 0.5f, "%.2f"));
REXCVAR_SET(skate3_native_render_scene_bloom_intensity,
CvarSlider("bloom intensity",
REXCVAR_GET(skate3_native_render_scene_bloom_intensity),
0.0f, 0.3f, "%.3f"));
REXCVAR_SET(skate3_native_render_scene_ssao,
CvarCheckbox("SSAO (GTAO)",
REXCVAR_GET(skate3_native_render_scene_ssao),
@ -604,6 +708,48 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
0.0f, 3.0f, "%.2f",
"How strongly bright (sun-lit) surfaces resist SSAO "
"darkening (ambient-only approximation)."));
{
const bool was_on = REXCVAR_GET(skate3_native_render_scene_sun_override);
const bool now_on =
CvarCheckbox("Sun override (lighting lab)", was_on,
"Move the sun with the sliders below: dynamic CSM "
"shadows, the static world-shadow map, shadow receivers "
"and the volumetric shafts all follow. Baked lightmap "
"shade and the sky dome's painted sun stay put (game "
"content).");
if (now_on && !was_on) {
// Seed the sliders from the captured sun so enabling the override
// starts at the true position instead of jumping.
float sun[3];
skate3::native_scene::GetCapturedSunDir(sun);
const float kRad = 57.29577951f;
REXCVAR_SET(skate3_native_render_scene_sun_azimuth,
double(std::fmod(std::atan2(sun[0], sun[2]) * kRad + 360.0f,
360.0f)));
REXCVAR_SET(
skate3_native_render_scene_sun_elevation,
double(std::clamp(std::asin(std::clamp(sun[1], -1.0f, 1.0f)) * kRad,
2.0f, 88.0f)));
}
REXCVAR_SET(skate3_native_render_scene_sun_override, now_on);
}
REXCVAR_SET(skate3_native_render_scene_sun_azimuth,
CvarSlider("sun azimuth (deg)",
REXCVAR_GET(skate3_native_render_scene_sun_azimuth),
0.0f, 360.0f, "%.0f"));
REXCVAR_SET(skate3_native_render_scene_sun_elevation,
CvarSlider("sun elevation (deg)",
REXCVAR_GET(skate3_native_render_scene_sun_elevation),
2.0f, 88.0f, "%.0f",
"Low elevations give long shadows and the most visible "
"volumetric shafts."));
}
void DrawShadowsSection() {
REXCVAR_SET(skate3_native_render_scene_shadows,
CvarCheckbox("Dynamic shadows",
REXCVAR_GET(skate3_native_render_scene_shadows),
"Native CSM: skater/NPC/prop shadows onto the world"));
REXCVAR_SET(skate3_native_render_scene_shadow_static_casters,
CvarCheckbox(
"Static sun shadows",
@ -657,41 +803,152 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
"Raise if static geometry shows self-shadow acne (stipple "
"on sunlit ground/walls); lower if static shadows visibly "
"detach from their casters."));
{
const bool was_on = REXCVAR_GET(skate3_native_render_scene_sun_override);
const bool now_on =
CvarCheckbox("Sun override (lighting lab)", was_on,
"Move the sun with the sliders below: dynamic CSM "
"shadows, the static world-shadow map, shadow receivers "
"and the volumetric shafts all follow. Baked lightmap "
"shade and the sky dome's painted sun stay put (game "
"content).");
if (now_on && !was_on) {
// Seed the sliders from the captured sun so enabling the override
// starts at the true position instead of jumping.
float sun[3];
skate3::native_scene::GetCapturedSunDir(sun);
const float kRad = 57.29577951f;
REXCVAR_SET(skate3_native_render_scene_sun_azimuth,
double(std::fmod(std::atan2(sun[0], sun[2]) * kRad + 360.0f,
360.0f)));
}
void DrawWorldShadingSection() {
REXCVAR_SET(skate3_native_render_scene_lightmaps,
CvarCheckbox("Lightmaps", REXCVAR_GET(skate3_native_render_scene_lightmaps),
"Baked lighting atlas sample x2 on world materials"));
REXCVAR_SET(skate3_native_render_scene_macro,
CvarCheckbox("Macro overlay", REXCVAR_GET(skate3_native_render_scene_macro),
"Large-scale grime/crack multiply (ground/wall weathering)"));
REXCVAR_SET(skate3_native_render_scene_decals,
CvarCheckbox("Decal art composite",
REXCVAR_GET(skate3_native_render_scene_decals),
"Graffiti/paint art lerped over environment.decal sections"));
REXCVAR_SET(skate3_native_render_scene_transparents,
CvarCheckbox("Transparent sub-pass",
REXCVAR_GET(skate3_native_render_scene_transparents),
"environment.transparent items (mist sheets, glass, fences)"));
REXCVAR_SET(skate3_native_render_scene_backface_cull,
CvarCheckbox("Backface cull (game parity)",
REXCVAR_GET(skate3_native_render_scene_backface_cull),
"World env materials cull FRONT like the game's material "
"XMLs; off = legacy cull-none (shows interior faces)"));
}
void DrawSceneContentSection() {
REXCVAR_SET(skate3_native_render_scene_world_items,
CvarCheckbox("World items", REXCVAR_GET(skate3_native_render_scene_world_items),
"Static geometry from the world sort lists"));
REXCVAR_SET(skate3_native_render_scene_dynamic_items,
CvarCheckbox("Dynamic items",
REXCVAR_GET(skate3_native_render_scene_dynamic_items),
"Characters, movable props, cloth (RenderMesh/world-path "
"captures)"));
REXCVAR_SET(
skate3_native_render_scene_sun_elevation,
double(std::clamp(std::asin(std::clamp(sun[1], -1.0f, 1.0f)) * kRad,
2.0f, 88.0f)));
skate3_native_render_scene_lw_fade,
CvarCheckbox("LW entity fade (store)",
REXCVAR_GET(skate3_native_render_scene_lw_fade),
"Serve NPC/traffic fade alpha from the LivingWorld entity "
"itself (per-instance store) instead of the per-draw "
"captured constant row; fixes opaque mid-air spawns, "
"missing fade-ins and clone alpha blinks"));
REXCVAR_SET(
skate3_native_render_scene_lw_identity,
CvarCheckbox("LW entity identity (pose rings)",
REXCVAR_GET(skate3_native_render_scene_lw_identity),
"Key NPC/traffic pose-smoothing rings by the game's own "
"per-instance MeshContext instead of (mesh, occurrence) "
"pairing; clone reshuffles can no longer mispair "
"(teleport/slide class)"));
REXCVAR_SET(
skate3_native_render_scene_lw_gap_fill,
CvarCheckbox("LW gap fill (1-2 frame republish)",
REXCVAR_GET(skate3_native_render_scene_lw_gap_fill),
"Republish a live NPC whose MeshContext skipped this "
"frame's submit records (the 1-3 frame publish GAPs that "
"read as blinks/small teleports)"));
REXCVAR_SET(
skate3_native_render_scene_lw_palette,
CvarCheckbox("LW authoritative caster palettes",
REXCVAR_GET(skate3_native_render_scene_lw_palette),
"Replace GUESSED ortho caster-bank palettes on "
"edge-of-view vehicles with the entity's own packed "
"palette from the pack writer (the mangle/transform "
"class)"));
REXCVAR_SET(skate3_native_render_scene_quadlists,
CvarCheckbox("Quad-list particles",
REXCVAR_GET(skate3_native_render_scene_quadlists),
"Non-indexed quad-list captures (particle systems; off by "
"default: render as white squares without sprite textures)"));
REXCVAR_SET(
skate3_native_render_scene_retain_offscreen,
CvarCheckbox("Retain off-screen statics",
REXCVAR_GET(skate3_native_render_scene_retain_offscreen),
"Keep recently seen statics drawn while the game view-culls "
"them: the smoothed render camera trails the guest pose, so "
"without this world geometry visibly tears down right at the "
"screen edges during pans/traversal"));
REXCVAR_SET(skate3_native_render_scene_2d,
CvarCheckbox("2D / HUD replay", REXCVAR_GET(skate3_native_render_scene_2d),
"APT/Flash HUD + glyph text + SimpleDraw icons"));
REXCVAR_SET(skate3_native_render_scene_splines,
CvarCheckbox("Neon splines", REXCVAR_GET(skate3_native_render_scene_splines),
"Waypoint arrows / marker beams"));
}
void DrawPacingSection() {
REXCVAR_SET(skate3_native_render_scene_smooth_camera,
CvarCheckbox("Smooth camera + entity poses",
REXCVAR_GET(skate3_native_render_scene_smooth_camera),
"The guest updates its camera/entities on its own ~200 Hz "
"sim tick; raw poses judder at high render rates. Re-times "
"them on the host clock (1 kHz camera sampler + pose "
"interpolation, a few ms of camera latency)."));
{
float w = float(REXCVAR_GET(skate3_native_render_scene_smooth_camera_filter_ms));
if (ImGui::SliderFloat("camera filter (ms, 0 = off)", &w, 0.0f, 100.0f, "%.0f")) {
REXCVAR_SET(skate3_native_render_scene_smooth_camera_filter_ms, double(w));
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Boxcar average over the camera pose signal. The game's camera "
"advances in 60 Hz-quantized lumps at high fps (the measured cause "
"of stick-pan stutter); 50 ms = three 60 Hz periods cancels it "
"exactly for ~25 ms extra camera latency. 0 reverts to raw "
"interpolation.");
}
}
REXCVAR_SET(skate3_native_render_scene_sort_opaque,
CvarCheckbox("Front-to-back opaque sort",
REXCVAR_GET(skate3_native_render_scene_sort_opaque),
"Early-z rejects occluded pixels before the material shading"));
{
int cap = int(REXCVAR_GET(skate3_guest_fps_cap));
if (ImGui::InputInt("Guest fps cap (0 = off)", &cap, 10, 30)) {
if (cap < 0) cap = 0;
if (cap > 1000) cap = 1000;
REXCVAR_SET(skate3_guest_fps_cap, double(cap));
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Pace guest frames on an even beat. Set a few fps below the display "
"refresh (with G-Sync/VRR this is what makes motion read as smooth; "
"uncapped, the guest's irregular frame times drive the refresh "
"directly).");
}
}
}
void DrawDiagnosticsSection() {
{
int mode = REXCVAR_GET(skate3_native_render_scene_debug);
const char* kModes[] = {"0: normal", "1: clear only", "2: solid colors",
"3: first 20 items", "4: no depth"};
if (ImGui::Combo("debug mode", &mode, kModes, 5)) {
REXCVAR_SET(skate3_native_render_scene_debug, mode);
}
}
{
int dbg = REXCVAR_GET(skate3_native_render_scene_hdr_debug);
const char* kHdrDbg[] = {"0: off", "1: bloom term",
"2: raw pre-tonemap", "3: AO plane",
"4: shaft plane", "5: haze term"};
if (ImGui::Combo("HDR debug view", &dbg, kHdrDbg, 6)) {
REXCVAR_SET(skate3_native_render_scene_hdr_debug, dbg);
}
REXCVAR_SET(skate3_native_render_scene_sun_override, now_on);
}
REXCVAR_SET(skate3_native_render_scene_sun_azimuth,
CvarSlider("sun azimuth (deg)",
REXCVAR_GET(skate3_native_render_scene_sun_azimuth),
0.0f, 360.0f, "%.0f"));
REXCVAR_SET(skate3_native_render_scene_sun_elevation,
CvarSlider("sun elevation (deg)",
REXCVAR_GET(skate3_native_render_scene_sun_elevation),
2.0f, 88.0f, "%.0f",
"Low elevations give long shadows and the most visible "
"volumetric shafts."));
ImGui::SeparatorText("Reflective glass isolation (env fam 5/6/13)");
{
@ -755,108 +1012,7 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
}
}
ImGui::SeparatorText("Scene items");
REXCVAR_SET(skate3_native_render_scene_world_items,
CvarCheckbox("World items", REXCVAR_GET(skate3_native_render_scene_world_items),
"Static geometry from the world sort lists"));
REXCVAR_SET(skate3_native_render_scene_dynamic_items,
CvarCheckbox("Dynamic items",
REXCVAR_GET(skate3_native_render_scene_dynamic_items),
"Characters, movable props, cloth (RenderMesh/world-path "
"captures)"));
REXCVAR_SET(
skate3_native_render_scene_lw_fade,
CvarCheckbox("LW entity fade (store)",
REXCVAR_GET(skate3_native_render_scene_lw_fade),
"Serve NPC/traffic fade alpha from the LivingWorld entity "
"itself (per-instance store) instead of the per-draw "
"captured constant row; fixes opaque mid-air spawns, "
"missing fade-ins and clone alpha blinks"));
REXCVAR_SET(
skate3_native_render_scene_lw_identity,
CvarCheckbox("LW entity identity (pose rings)",
REXCVAR_GET(skate3_native_render_scene_lw_identity),
"Key NPC/traffic pose-smoothing rings by the game's own "
"per-instance MeshContext instead of (mesh, occurrence) "
"pairing; clone reshuffles can no longer mispair "
"(teleport/slide class)"));
REXCVAR_SET(
skate3_native_render_scene_lw_gap_fill,
CvarCheckbox("LW gap fill (1-2 frame republish)",
REXCVAR_GET(skate3_native_render_scene_lw_gap_fill),
"Republish a live NPC whose MeshContext skipped this "
"frame's submit records (the 1-3 frame publish GAPs that "
"read as blinks/small teleports)"));
REXCVAR_SET(
skate3_native_render_scene_lw_palette,
CvarCheckbox("LW authoritative caster palettes",
REXCVAR_GET(skate3_native_render_scene_lw_palette),
"Replace GUESSED ortho caster-bank palettes on "
"edge-of-view vehicles with the entity's own packed "
"palette from the pack writer (the mangle/transform "
"class)"));
REXCVAR_SET(skate3_native_render_scene_quadlists,
CvarCheckbox("Quad-list particles",
REXCVAR_GET(skate3_native_render_scene_quadlists),
"Non-indexed quad-list captures (particle systems; off by "
"default: render as white squares without sprite textures)"));
REXCVAR_SET(
skate3_native_render_scene_retain_offscreen,
CvarCheckbox("Retain off-screen statics",
REXCVAR_GET(skate3_native_render_scene_retain_offscreen),
"Keep recently seen statics drawn while the game view-culls "
"them: the smoothed render camera trails the guest pose, so "
"without this world geometry visibly tears down right at the "
"screen edges during pans/traversal"));
ImGui::SeparatorText("Overlays");
REXCVAR_SET(skate3_native_render_scene_2d,
CvarCheckbox("2D / HUD replay", REXCVAR_GET(skate3_native_render_scene_2d),
"APT/Flash HUD + glyph text + SimpleDraw icons"));
REXCVAR_SET(skate3_native_render_scene_splines,
CvarCheckbox("Neon splines", REXCVAR_GET(skate3_native_render_scene_splines),
"Waypoint arrows / marker beams"));
ImGui::SeparatorText("Smoothness / pacing");
REXCVAR_SET(skate3_native_render_scene_smooth_camera,
CvarCheckbox("Smooth camera + entity poses",
REXCVAR_GET(skate3_native_render_scene_smooth_camera),
"The guest updates its camera/entities on its own ~200 Hz "
"sim tick; raw poses judder at high render rates. Re-times "
"them on the host clock (1 kHz camera sampler + pose "
"interpolation, a few ms of camera latency)."));
{
float w = float(REXCVAR_GET(skate3_native_render_scene_smooth_camera_filter_ms));
if (ImGui::SliderFloat("camera filter (ms, 0 = off)", &w, 0.0f, 100.0f, "%.0f")) {
REXCVAR_SET(skate3_native_render_scene_smooth_camera_filter_ms, double(w));
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Boxcar average over the camera pose signal. The game's camera "
"advances in 60 Hz-quantized lumps at high fps (the measured cause "
"of stick-pan stutter); 50 ms = three 60 Hz periods cancels it "
"exactly for ~25 ms extra camera latency. 0 reverts to raw "
"interpolation.");
}
}
REXCVAR_SET(skate3_native_render_scene_sort_opaque,
CvarCheckbox("Front-to-back opaque sort",
REXCVAR_GET(skate3_native_render_scene_sort_opaque),
"Early-z rejects occluded pixels before the material shading"));
{
int cap = int(REXCVAR_GET(skate3_guest_fps_cap));
if (ImGui::InputInt("Guest fps cap (0 = off)", &cap, 10, 30)) {
if (cap < 0) cap = 0;
if (cap > 1000) cap = 1000;
REXCVAR_SET(skate3_guest_fps_cap, double(cap));
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Pace guest frames on an even beat. Set a few fps below the display "
"refresh (with G-Sync/VRR this is what makes motion read as smooth; "
"uncapped, the guest's irregular frame times drive the refresh "
"directly).");
}
}
ImGui::SeparatorText("Judder isolation");
{
int mode = REXCVAR_GET(skate3_native_render_scene_synthetic_pan);
const char* kPanModes[] = {"0: off", "1: time-based (build clock)",
@ -888,6 +1044,15 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
if (ImGui::Button("Record camera signal (8 s)")) {
skate3::native_scene::RecordCameraSignal(8.0);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Click, then IMMEDIATELY pan the camera with the right stick at a "
"steady rate for 8 seconds (synthetic pan should be OFF). Records "
"every distinct guest camera pose with 1 kHz-sampler timestamps + "
"the smoothed output, to logs\\cam_signal_<ts>.csv; offline "
"analysis shows whether the game's own camera signal is jerky at "
"the source.");
}
ImGui::SameLine();
if (ImGui::Button("Record bone signal (6 s)")) {
skate3::native_scene::RecordBoneSignal(6.0);
@ -900,18 +1065,10 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
"deck lag/orbit can be measured and candidate fixes replayed "
"offline against real data.");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Click, then IMMEDIATELY pan the camera with the right stick at a "
"steady rate for 8 seconds (synthetic pan should be OFF). Records "
"every distinct guest camera pose with 1 kHz-sampler timestamps + "
"the smoothed output, to logs\\cam_signal_<ts>.csv; offline "
"analysis shows whether the game's own camera signal is jerky at "
"the source.");
}
}
ImGui::SeparatorText("Caches (lightmap-era plumbing)");
void DrawCachesSection() {
REXCVAR_SET(
skate3_native_render_scene_tex_revalidate,
CvarCheckbox("Texture payload revalidation",
@ -935,7 +1092,96 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
if (ImGui::Button("Flush mesh cache")) {
skate3::native_scene::FlushMeshCache();
}
ImGui::TextDisabled("MSAA + shadow tile size are restart-only (skate3.toml).");
}
} // namespace
void NativeDebugDialog::Show() {
visible_ = true;
SetDrawActive(true);
}
void NativeDebugDialog::Hide() {
if (!visible_) {
return;
}
visible_ = false;
SetDrawActive(false);
}
void NativeDebugDialog::Toggle() {
if (visible_) {
Hide();
} else {
Show();
}
}
void NativeDebugDialog::OnDraw(ImGuiIO& io) {
(void)io;
if (!visible_) {
return;
}
bool open = visible_;
ImGui::SetNextWindowSize(ImVec2(430.0f, 0.0f), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Native Render Debug (F12)", &open, ImGuiWindowFlags_NoCollapse)) {
ImGui::End();
if (!open) {
Hide();
}
return;
}
// Renderer master switches stay visible above the sections: they gate
// everything below and are the first thing checked when bisecting.
if (!REXCVAR_GET(skate3_native_render)) {
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.3f, 1.0f),
"skate3_native_render hook layer is OFF (boot-time)");
}
{
const bool v = CvarCheckbox("Native scene renderer (F5)",
REXCVAR_GET(skate3_native_render_scene),
"Full native/emulated switch, same as F5");
if (v != REXCVAR_GET(skate3_native_render_scene)) {
skate3::native_scene::ToggleSceneEnabled();
}
}
REXCVAR_SET(native_render_suppress_emulated_draws,
CvarCheckbox("Suppress emulated draws",
REXCVAR_GET(native_render_suppress_emulated_draws),
"Skip emulated GPU work for framebuffer-sized passes while "
"native output is active (perf). Small-surface passes "
"(lightmap page composition) always run."));
if (ImGui::CollapsingHeader("Showcase & capture",
ImGuiTreeNodeFlags_DefaultOpen)) {
DrawShowcaseCaptureSection();
}
if (ImGui::CollapsingHeader("Image quality",
ImGuiTreeNodeFlags_DefaultOpen)) {
DrawImageQualitySection();
}
if (ImGui::CollapsingHeader("Lighting & post")) {
DrawLightingPostSection();
}
if (ImGui::CollapsingHeader("Shadows")) {
DrawShadowsSection();
}
if (ImGui::CollapsingHeader("World shading")) {
DrawWorldShadingSection();
}
if (ImGui::CollapsingHeader("Scene content & overlays")) {
DrawSceneContentSection();
}
if (ImGui::CollapsingHeader("Smoothness & pacing")) {
DrawPacingSection();
}
if (ImGui::CollapsingHeader("Diagnostics")) {
DrawDiagnosticsSection();
}
if (ImGui::CollapsingHeader("Caches")) {
DrawCachesSection();
}
ImGui::End();
if (s_showcase_setup_open) {

View file

@ -1,8 +1,10 @@
#pragma once
// F12 native-render debug menu: live checkboxes for every native renderer
// subsystem (all hot-reload cvars), used to bisect visual regressions
// (flicker, wrong shading) in-game without rebuilds.
// F12 native-render debug menu: live controls for every native renderer
// subsystem (all hot-reload cvars), grouped by intent (capture tools,
// image quality, lighting, shadows, content, diagnostics), used to bisect
// visual regressions in-game without rebuilds. Includes the max-quality
// photo-mode toggle for showcase/promo recording.
#include <rex/ui/imgui_dialog.h>