#include "skate3_native_debug_dialog.h" #include #include #include #include #include #include #include #include #include #include "skate3_native_scene.h" REXCVAR_DEFINE_BOOL(skate3_native_render_mode_indicator, false, "Skate 3", "Small top-right corner readout of which renderer produced the " "last presented frame: NATIVE (native scene renderer) or " "EMULATED (Xenos GPU emulation; menus/loading yields, F5 off). " "Shows automatically while the native scene renderer is " "switched off, regardless of this setting.") .lifecycle(rex::cvar::Lifecycle::kHotReload); // Hook-layer master (boot-time), shown read-only. REXCVAR_DECLARE(bool, skate3_native_render); // Hot-reload feature gates (skate3_native_scene.cpp). REXCVAR_DECLARE(bool, skate3_native_render_scene); REXCVAR_DECLARE(bool, skate3_native_render_scene_lightmaps); REXCVAR_DECLARE(bool, skate3_native_render_scene_macro); REXCVAR_DECLARE(bool, skate3_native_render_scene_decals); REXCVAR_DECLARE(bool, skate3_native_render_scene_transparents); REXCVAR_DECLARE(bool, skate3_native_render_scene_shadows); REXCVAR_DECLARE(bool, skate3_native_render_scene_backface_cull); REXCVAR_DECLARE(bool, skate3_native_render_scene_2d); REXCVAR_DECLARE(bool, skate3_native_render_scene_splines); REXCVAR_DECLARE(bool, skate3_native_render_scene_quadlists); REXCVAR_DECLARE(bool, skate3_native_render_scene_world_items); REXCVAR_DECLARE(bool, skate3_native_render_scene_dynamic_items); REXCVAR_DECLARE(bool, skate3_native_render_scene_lw_fade); REXCVAR_DECLARE(bool, skate3_native_render_scene_lw_identity); REXCVAR_DECLARE(bool, skate3_native_render_scene_lw_gap_fill); REXCVAR_DECLARE(bool, skate3_native_render_scene_lw_palette); REXCVAR_DECLARE(bool, skate3_native_render_scene_retain_offscreen); 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); REXCVAR_DECLARE(bool, skate3_native_render_scene_bloom); REXCVAR_DECLARE(double, skate3_native_render_scene_bloom_threshold); REXCVAR_DECLARE(double, skate3_native_render_scene_bloom_knee); REXCVAR_DECLARE(double, skate3_native_render_scene_bloom_intensity); REXCVAR_DECLARE(bool, skate3_native_render_scene_shafts); REXCVAR_DECLARE(double, skate3_native_render_scene_shafts_intensity); REXCVAR_DECLARE(double, skate3_native_render_scene_shafts_reach); REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shafts_steps); REXCVAR_DECLARE(bool, skate3_native_render_scene_haze); REXCVAR_DECLARE(double, skate3_native_render_scene_haze_intensity); REXCVAR_DECLARE(double, skate3_native_render_scene_haze_density); REXCVAR_DECLARE(bool, skate3_native_render_scene_ssao); REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_radius); REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_intensity); REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_luma_protect); REXCVAR_DECLARE(bool, skate3_native_render_scene_shadow_static_casters); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_strength); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_radius); REXCVAR_DECLARE(bool, skate3_native_render_scene_shadow_pcss); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_pcss_sun_deg); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_pcss_max_m); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_bias); REXCVAR_DECLARE(bool, skate3_native_render_scene_sun_override); REXCVAR_DECLARE(double, skate3_native_render_scene_sun_azimuth); REXCVAR_DECLARE(double, skate3_native_render_scene_sun_elevation); REXCVAR_DECLARE(int32_t, skate3_native_render_scene_refl_mode); REXCVAR_DECLARE(double, skate3_native_render_scene_refl_lod); REXCVAR_DECLARE(double, skate3_native_render_scene_refl_bias_x); REXCVAR_DECLARE(double, skate3_native_render_scene_refl_bias_y); REXCVAR_DECLARE(bool, skate3_native_render_scene_refl_bias_auto); // Smoothness / pacing. REXCVAR_DECLARE(bool, skate3_native_render_scene_smooth_camera); REXCVAR_DECLARE(double, skate3_native_render_scene_smooth_camera_filter_ms); REXCVAR_DECLARE(bool, skate3_native_render_scene_sort_opaque); REXCVAR_DECLARE(double, skate3_guest_fps_cap); REXCVAR_DECLARE(int32_t, skate3_native_render_scene_synthetic_pan); REXCVAR_DECLARE(double, skate3_native_render_scene_synthetic_pan_rate); REXCVAR_DECLARE(double, skate3_native_render_scene_synthetic_pan_amp); // SDK: emulated-draw suppression while the native output is active. REXCVAR_DECLARE(bool, native_render_suppress_emulated_draws); REXCVAR_DECLARE(bool, skate3_native_render_scene_showcase); REXCVAR_DECLARE(double, skate3_native_render_scene_showcase_hold); REXCVAR_DECLARE(double, skate3_native_render_scene_showcase_wipe); REXCVAR_DECLARE(std::string, skate3_native_render_scene_showcase_order); REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam); 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 { bool CvarCheckbox(const char* label, bool value, const char* help = nullptr) { bool v = value; ImGui::Checkbox(label, &v); if (help != nullptr && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", help); } return v; } // Float-cvar slider (the cvars are hot-reload doubles; edits apply next // frame). Returns the possibly-changed value for REXCVAR_SET. double CvarSlider(const char* label, double value, float lo, float hi, const char* fmt, const char* help = nullptr) { float v = float(value); if (ImGui::SliderFloat(label, &v, lo, hi, fmt)) { value = double(v); } if (help != nullptr && (ImGui::IsItemHovered() || ImGui::IsItemActive())) { ImGui::SetTooltip("%s", help); } 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; } REXCVAR_DEFINE_INT32( skate3_native_render_scene_maxq_cycle, 0, "Skate 3", "DEBUG: auto-toggle MAX QUALITY every N native-scene frames (0 = off). " "Stress-tests the hot pipeline/target/shadow-map rebuild path; pair " "with Vulkan validation layers when hunting stale bindings.") .range(0, 1000000) .lifecycle(rex::cvar::Lifecycle::kHotReload); 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 // cvar string is the single source of truth ("tok,tok+tok,-tok": ',' starts // a new wipe, '+' joins, '-' disables in place); the rows parse from it // every frame and any edit writes it straight back, so the sequencer, this // window and hand edits in config all stay consistent. bool s_showcase_setup_open = false; struct ShowcaseRow { const skate3::native_scene::ShowcaseLayer* layer; bool enabled; bool joined; }; std::vector ParseShowcaseRows(const std::string& order) { using skate3::native_scene::kShowcaseLayers; std::vector rows; std::string tok; bool cur_joined = false; // the separator before the accumulating token const auto flush = [&]() { while (!tok.empty() && (tok.front() == ' ' || tok.front() == '\t')) { tok.erase(tok.begin()); } while (!tok.empty() && (tok.back() == ' ' || tok.back() == '\t')) { tok.pop_back(); } bool enabled = true; if (!tok.empty() && tok[0] == '-') { enabled = false; tok.erase(tok.begin()); } for (char& ch : tok) { if (ch >= 'A' && ch <= 'Z') { ch = char(ch + 32); } } for (const auto& layer : kShowcaseLayers) { if (tok != layer.token) { continue; } bool present = false; for (const auto& row : rows) { present = present || row.layer == &layer; } if (!present) { rows.push_back({&layer, enabled, !rows.empty() && cur_joined}); } break; } tok.clear(); }; for (char ch : order) { if (ch == ',' || ch == '+') { flush(); cur_joined = ch == '+'; } else { tok += ch; } } flush(); // Layers missing from the string append disabled, so they can always be // re-enabled from the window. for (const auto& layer : kShowcaseLayers) { bool present = false; for (const auto& row : rows) { present = present || row.layer == &layer; } if (!present) { rows.push_back({&layer, false, false}); } } return rows; } std::string BuildShowcaseOrder(const std::vector& rows) { std::string out; for (size_t k = 0; k < rows.size(); ++k) { if (k > 0) { out += rows[k].joined ? '+' : ','; } if (!rows[k].enabled) { out += '-'; } out += rows[k].layer->token; } return out; } // Mirrors the sequencer's availability rules (TickShowcase): the material // looks need nothing; shadow/post layers need their feature (post gates // live in the HDR tonemap). bool ShowcaseLayerAvailable(uint32_t bit) { const bool hdr_on = REXCVAR_GET(skate3_native_render_scene_hdr); switch (bit) { case 8u: return REXCVAR_GET(skate3_native_render_scene_shadows); case 16u: return hdr_on && REXCVAR_GET(skate3_native_render_scene_ssao); case 32u: return hdr_on && REXCVAR_GET(skate3_native_render_scene_ssr); case 64u: return hdr_on && (REXCVAR_GET(skate3_native_render_scene_shafts) || REXCVAR_GET(skate3_native_render_scene_haze)); case 128u: return hdr_on && REXCVAR_GET(skate3_native_render_scene_bloom); default: return true; } } // Drone-camera controls (same cvars everywhere the section is embedded; // edits apply live). void DrawFreecamControls() { ImGui::SeparatorText("Drone camera"); bool freecam = REXCVAR_GET(skate3_native_render_scene_freecam); if (ImGui::Checkbox("Free-fly drone cam (End)", &freecam)) { REXCVAR_SET(skate3_native_render_scene_freecam, freecam); } ImGui::SameLine(); bool capture = REXCVAR_GET(skate3_native_render_scene_freecam_capture_input); if (ImGui::Checkbox("capture game input", &capture)) { REXCVAR_SET(skate3_native_render_scene_freecam_capture_input, capture); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "While flying, keep keyboard/controller input away from the game so\n" "the fly keys don't also steer the skater."); } REXCVAR_SET(skate3_native_render_scene_freecam_speed, CvarSlider("fly speed (m/s)", REXCVAR_GET(skate3_native_render_scene_freecam_speed), 0.5f, 60.0f, "%.1f", "Base fly speed; Shift = 4x, Ctrl = 0.2x")); REXCVAR_SET( skate3_native_render_scene_freecam_look_speed, CvarSlider("look speed (deg/s)", REXCVAR_GET(skate3_native_render_scene_freecam_look_speed), 20.0f, 240.0f, "%.0f", "Arrow-key look rate")); ImGui::TextDisabled( "WASD fly, E/Space up, Q/C down, arrows or right-mouse drag look,\n" "Z/X zoom, Shift fast, Ctrl slow."); } void DrawShowcaseSetupWindow(bool* p_open) { using skate3::native_scene::kShowcaseLayers; ImGui::SetNextWindowSize(ImVec2(430.0f, 0.0f), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Showcase Setup", p_open, ImGuiWindowFlags_NoCollapse)) { ImGui::End(); return; } const bool running = REXCVAR_GET(skate3_native_render_scene_showcase); if (ImGui::Button(running ? "Cancel showcase" : "Start showcase (Ctrl+Shift+B)", ImVec2(-1.0f, 0.0f))) { REXCVAR_SET(skate3_native_render_scene_showcase, !running); } REXCVAR_SET(skate3_native_render_scene_showcase_hold, CvarSlider("stage hold (s)", REXCVAR_GET(skate3_native_render_scene_showcase_hold), 0.0f, 10.0f, "%.1f", "Seconds each layer holds fullscreen between wipes")); REXCVAR_SET(skate3_native_render_scene_showcase_wipe, CvarSlider("wipe duration (s)", REXCVAR_GET(skate3_native_render_scene_showcase_wipe), 0.5f, 10.0f, "%.1f", "Seconds each split wipe takes to cross the screen")); ImGui::SeparatorText("Layer order"); ImGui::TextDisabled( "Every run starts from clay geometry. \"join\" reveals a layer\n" "inside the previous layer's wipe instead of its own."); std::vector rows = ParseShowcaseRows(REXCVAR_GET(skate3_native_render_scene_showcase_order)); bool changed = false; for (int k = 0; k < int(rows.size()); ++k) { ImGui::PushID(k); bool en = rows[k].enabled; if (ImGui::Checkbox("##on", &en)) { rows[k].enabled = en; changed = true; } ImGui::SameLine(); ImGui::BeginDisabled(k == 0); if (ImGui::ArrowButton("up", ImGuiDir_Up) && k > 0) { std::swap(rows[k - 1], rows[k]); changed = true; } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(k + 1 == int(rows.size())); if (ImGui::ArrowButton("down", ImGuiDir_Down) && k + 1 < int(rows.size())) { std::swap(rows[k], rows[k + 1]); changed = true; } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(k == 0); bool joined = k > 0 && rows[k].joined; if (ImGui::Checkbox("join", &joined)) { rows[k].joined = joined; changed = true; } ImGui::EndDisabled(); ImGui::SameLine(); if (ShowcaseLayerAvailable(rows[k].layer->bit)) { ImGui::TextUnformatted(rows[k].layer->label); } else { ImGui::TextDisabled("%s (feature off)", rows[k].layer->label); } ImGui::PopID(); } if (changed) { if (!rows.empty()) { rows[0].joined = false; } REXCVAR_SET(skate3_native_render_scene_showcase_order, BuildShowcaseOrder(rows)); } if (ImGui::Button("Reset to default order")) { REXCVAR_SET(skate3_native_render_scene_showcase_order, std::string(skate3::native_scene::kShowcaseOrderDefault)); } // Live preview of the resulting run, mirrors the sequencer's step // builder (cumulative layer masks; materials subsumes albedo/lighting; // a final full-render step covers whatever the list leaves out). ImGui::SeparatorText("Run preview"); uint32_t avail_mask = 0; for (const auto& layer : kShowcaseLayers) { if (ShowcaseLayerAvailable(layer.bit)) { avail_mask |= layer.bit; } } int step_no = 1; ImGui::Text("%d. clay geometry", step_no++); uint32_t cum = 0; size_t k = 0; while (k < rows.size()) { uint32_t add = 0; std::string label; size_t j = k; do { const ShowcaseRow& row = rows[j]; if (row.enabled && (avail_mask & row.layer->bit) != 0 && (cum & row.layer->bit) == 0 && (add & row.layer->bit) == 0) { add |= row.layer->bit; if (!label.empty()) { label += " & "; } label += row.layer->label; } ++j; } while (j < rows.size() && rows[j].joined); if (add != 0) { cum |= add; ImGui::Text("%d. + %s", step_no++, label.c_str()); } k = j; } const bool covered = (cum & 4u) != 0 && ((avail_mask & ~cum) & 0xF8u) == 0u; if (!covered) { ImGui::Text("%d. full render", step_no++); } DrawFreecamControls(); ImGui::End(); } // ---- 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 DrawShowcaseCaptureSection() { DrawMaxQualityToggle(); { const bool running = REXCVAR_GET(skate3_native_render_scene_showcase); if (ImGui::Button(running ? "Cancel showcase" : "Start showcase (Ctrl+Shift+B)")) { REXCVAR_SET(skate3_native_render_scene_showcase, !running); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "Strip the frame to clay geometry, then rebuild it layer by\n" "layer, each layer revealed by a live vertical split wiping\n" "across the screen. Layer order, grouping and pacing are edited\n" "in the setup window."); } ImGui::SameLine(); if (ImGui::Button("Setup...")) { s_showcase_setup_open = true; } } DrawFreecamControls(); } 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).")); 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), "Shadow-marched air: shadowed portions of each view ray " "proportionally dim the light seen through them (dark " "crepuscular shafts from buildings/trees/underpasses). " "Fully lit air is untouched.")); REXCVAR_SET(skate3_native_render_scene_shafts_intensity, CvarSlider("shaft intensity", REXCVAR_GET(skate3_native_render_scene_shafts_intensity), 0.0f, 1.5f, "%.2f", "How strongly fully shadowed air dims the scene behind it " "(scaled by the forward-scatter phase toward the sun).")); REXCVAR_SET(skate3_native_render_scene_shafts_reach, CvarSlider("shaft reach (world units)", REXCVAR_GET(skate3_native_render_scene_shafts_reach), 5.0f, 300.0f, "%.0f", "How far in front of the camera the air is sampled. Longer " "= more distant shadow volumes, coarser sampling.")); { int steps = REXCVAR_GET(skate3_native_render_scene_shafts_steps); if (ImGui::SliderInt("shaft steps", &steps, 8, 64)) { REXCVAR_SET(skate3_native_render_scene_shafts_steps, steps); } } REXCVAR_SET(skate3_native_render_scene_haze, CvarCheckbox("Directional haze", REXCVAR_GET(skate3_native_render_scene_haze), "Fog-tinted sun scattering added with view distance, " "strongest toward the sun. Off by default: the game's " "authored sky/fog already carry the base atmosphere.")); REXCVAR_SET(skate3_native_render_scene_haze_intensity, CvarSlider("haze intensity", REXCVAR_GET(skate3_native_render_scene_haze_intensity), 0.0f, 0.5f, "%.3f")); REXCVAR_SET(skate3_native_render_scene_haze_density, CvarSlider("haze density (1/unit)", REXCVAR_GET(skate3_native_render_scene_haze_density), 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_ssao, CvarCheckbox("SSAO (GTAO)", REXCVAR_GET(skate3_native_render_scene_ssao), "Screen-space ambient occlusion: contact shading under " "ledges, rails, vehicles, feet.")); REXCVAR_SET(skate3_native_render_scene_ssao_radius, CvarSlider("SSAO radius (world units)", REXCVAR_GET(skate3_native_render_scene_ssao_radius), 0.1f, 4.0f, "%.2f")); REXCVAR_SET(skate3_native_render_scene_ssao_intensity, CvarSlider("SSAO intensity", REXCVAR_GET(skate3_native_render_scene_ssao_intensity), 0.0f, 3.0f, "%.2f", "Exponent on the visibility term (1 = physical, >1 = " "accentuated).")); REXCVAR_SET(skate3_native_render_scene_ssao_luma_protect, CvarSlider("SSAO sunlit protection", REXCVAR_GET(skate3_native_render_scene_ssao_luma_protect), 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", REXCVAR_GET(skate3_native_render_scene_shadow_static_casters), "Native static sun-shadow map: buildings, trees, rails and " "placed props cast live shadows from a dedicated " "sun-aligned depth map: shade on characters and props, " "and shadows that follow a moved sun. The game's own " "dynamic shadow cascades are untouched.")); REXCVAR_SET( skate3_native_render_scene_shadow_static_strength, CvarSlider( "static shadow strength", REXCVAR_GET(skate3_native_render_scene_shadow_static_strength), 0.0f, 1.0f, "%.2f", "How dark static-geometry shadows get (characters/props always " "cast at full strength). Lower if live static shade fights the " "baked lighting.")); REXCVAR_SET( skate3_native_render_scene_shadow_static_radius, CvarSlider( "static shadow radius (m)", REXCVAR_GET(skate3_native_render_scene_shadow_static_radius), 40.0f, 600.0f, "%.0f", "Far-cascade half-extent of the static sun-shadow map (mid and " "inner cascades cover 1/2 and 1/6 at 2x/6x density). Larger " "reaches farther at lower far-cascade density.")); REXCVAR_SET(skate3_native_render_scene_shadow_pcss, CvarCheckbox( "Soft shadows (PCSS)", REXCVAR_GET(skate3_native_render_scene_shadow_pcss), "Contact-hardening filter: crisp where a shadow touches " "its caster, progressively softer with caster height.")); REXCVAR_SET(skate3_native_render_scene_shadow_pcss_sun_deg, CvarSlider( "sun angular size (deg)", REXCVAR_GET(skate3_native_render_scene_shadow_pcss_sun_deg), 0.1f, 8.0f, "%.1f", "Penumbra growth per meter of caster height (the real sun " "is ~0.53 deg).")); REXCVAR_SET(skate3_native_render_scene_shadow_pcss_max_m, CvarSlider( "max penumbra (m)", REXCVAR_GET(skate3_native_render_scene_shadow_pcss_max_m), 0.05f, 5.0f, "%.2f")); REXCVAR_SET( skate3_native_render_scene_shadow_static_bias, CvarSlider("static receive bias (m)", REXCVAR_GET(skate3_native_render_scene_shadow_static_bias), 0.0f, 0.5f, "%.3f", "Raise if static geometry shows self-shadow acne (stipple " "on sunlit ground/walls); lower if static shadows visibly " "detach from their casters.")); } 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_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); } } ImGui::SeparatorText("Reflective glass isolation (env fam 5/6/13)"); { int mode = REXCVAR_GET(skate3_native_render_scene_refl_mode); const char* kReflModes[] = {"0: normal", "1: cube reflection OFF", "2: cube at absolute LOD (slider)", "3: flat normal (no normal map)", "4: visualize cube sample only", "5: body only (no spec, no cube)", "6: normal-map LOD bias (slider)", "7: visualize lightmap sample", "8: visualize lightmap UV (frac x16)", "9: lightmap resolve status (blue = missing)"}; if (ImGui::Combo("refl mode", &mode, kReflModes, 10)) { REXCVAR_SET(skate3_native_render_scene_refl_mode, mode); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "Live isolation of the glass-facade reflection term. Flip F5 to " "compare against the emulated frame at each setting:\n" "1 removes the cube term; if the artifact survives, it is NOT " "the reflection.\n2 + slider finds which mip level (if any) " "matches the emulated look.\n3 tests the normal-map perturbation." "\n4 shows exactly what the reflection vector samples.\n5 leaves " "only diffuse x lightmap."); } float lod = float(REXCVAR_GET(skate3_native_render_scene_refl_lod)); if (ImGui::SliderFloat("refl LOD / extra bias", &lod, -4.0f, 12.0f, "%.1f")) { REXCVAR_SET(skate3_native_render_scene_refl_lod, double(lod)); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "Mode 2: the absolute cube mip level (0 = sharpest; ~9 = the " "face average).\nOther modes: extra LOD bias added to the " "automatic 640p-parity bias."); } REXCVAR_SET(skate3_native_render_scene_refl_bias_auto, CvarCheckbox("auto normal tilt (derive from material)", REXCVAR_GET(skate3_native_render_scene_refl_bias_auto), "Compute the constant tilt from each material's own " "detail texture (hardware-exact BC1 decode), the " "principled source of the hand-tuned reference " "values. Uncheck to drive the sliders below " "instead.")); float bx = float(REXCVAR_GET(skate3_native_render_scene_refl_bias_x)); if (ImGui::SliderFloat("normal tilt X (horizontal)", &bx, -0.2f, 0.2f, "%.3f")) { REXCVAR_SET(skate3_native_render_scene_refl_bias_x, double(bx)); } float by = float(REXCVAR_GET(skate3_native_render_scene_refl_bias_y)); if (ImGui::SliderFloat("normal tilt Y (vertical)", &by, -0.2f, 0.2f, "%.3f")) { REXCVAR_SET(skate3_native_render_scene_refl_bias_y, double(by)); } if (ImGui::IsItemHovered() || ImGui::IsItemActive()) { ImGui::SetTooltip( "Constant normal tilt on the glass, applied in EVERY mode; " "rotates all reflections. Drag until the reflected content sits " "where the emulated frame puts it (F5 to compare). Defaults " "(0.028, 0.012) = the exact detail-map constant. 0.01 here is " "roughly 1 degree of reflection rotation."); } } ImGui::SeparatorText("Judder isolation"); { int mode = REXCVAR_GET(skate3_native_render_scene_synthetic_pan); const char* kPanModes[] = {"0: off", "1: time-based (build clock)", "2: fixed step per frame", "3: through smoother"}; if (ImGui::Combo("Synthetic pan (P)", &mode, kPanModes, 4)) { REXCVAR_SET(skate3_native_render_scene_synthetic_pan, mode); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "Judder isolation probe: replaces the camera with a host-generated " "constant-rate 360-degree pan. AFTER ENGAGING, SWEEP THE REAL CAMERA " "AROUND ONCE WITH THE STICK; the game only submits what its own " "frustum sees; the sweep fills a static-world union so the full " "circle is populated.\n1 smooth = camera path is fine; 1 judders = " "frame pacing / present / display, not the camera.\n2 = constant " "angle per FRAME (smooth only if displayed frames are evenly spaced " ", the complement of 1).\n3 = feeds synthetic ~200 Hz samples " "through the camera smoother and logs reconstruction error " "(synthetic-pan: lines)."); } float rate = float(REXCVAR_GET(skate3_native_render_scene_synthetic_pan_rate)); if (ImGui::SliderFloat("pan rate (deg/s)", &rate, 5.0f, 720.0f, "%.0f")) { REXCVAR_SET(skate3_native_render_scene_synthetic_pan_rate, double(rate)); } float amp = float(REXCVAR_GET(skate3_native_render_scene_synthetic_pan_amp)); if (ImGui::SliderFloat("pan amplitude (0 = full spin)", &, 0.0f, 60.0f, "%.0f")) { REXCVAR_SET(skate3_native_render_scene_synthetic_pan_amp, double(amp)); } 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_.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); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "Click, then skate at a steady speed for 6 seconds. Records every " "raw entity pose (bone palettes with timestamps) + the rendered " "interpolation output to logs\\bone_signal_.bin, so wheel-vs-" "deck lag/orbit can be measured and candidate fixes replayed " "offline against real data."); } } } void DrawCachesSection() { REXCVAR_SET( skate3_native_render_scene_tex_revalidate, CvarCheckbox("Texture payload revalidation", REXCVAR_GET(skate3_native_render_scene_tex_revalidate), "Re-fingerprint cached texture payloads every 16 frames, re-decode " "on change (added to heal late-composed lightmap pages)")); REXCVAR_SET(skate3_native_render_scene_mesh_revalidate, CvarCheckbox("Mesh payload revalidation", REXCVAR_GET(skate3_native_render_scene_mesh_revalidate), "Re-decode cached meshes when the guest payload fingerprint " "changes (streaming arena reuse / CPU-animated buffers)")); REXCVAR_SET(skate3_native_render_scene_tex_mips, CvarCheckbox("Texture mip chains", REXCVAR_GET(skate3_native_render_scene_tex_mips), "Upload guest mip chains (off = mip 0 only). Flush the " "texture cache after toggling.")); if (ImGui::Button("Flush texture cache")) { skate3::native_scene::FlushTextureCache(); } ImGui::SameLine(); if (ImGui::Button("Flush mesh cache")) { skate3::native_scene::FlushMeshCache(); } } } // namespace void MaxQualityAutoCycle(uint64_t frame_number) { const int32_t period = REXCVAR_GET(skate3_native_render_scene_maxq_cycle); if (period <= 0) { return; } static uint64_t next_frame = 0; if (next_frame == 0) { next_frame = frame_number + uint64_t(period); return; } if (frame_number >= next_frame) { next_frame = frame_number + uint64_t(period); SetMaxQuality(!s_max_quality.active); REXLOG_INFO("native-scene: max-quality auto-cycle -> {}", s_max_quality.active ? "ON" : "OFF"); } } 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) { DrawShowcaseSetupWindow(&s_showcase_setup_open); } if (!open) { Hide(); } } void RenderModeIndicator::OnDraw(ImGuiIO& io) { // Force-show while the native scene renderer is switched off (F5, the // settings Renderer row, or the boot-time hook-layer master) or has hit an // unrecoverable failure and permanently yields to the emulated output: // running emulated is a degraded state the player should be able to see // even with the indicator cvar off. Natural per-frame yields while the // scene renderer is enabled (menus/loading) do not trigger this. const bool scene_off = !REXCVAR_GET(skate3_native_render) || !REXCVAR_GET(skate3_native_render_scene) || skate3::native_scene::SceneFailed(); if (!REXCVAR_GET(skate3_native_render_mode_indicator) && !scene_off) { return; } // Pre-runtime (installer wizards) no guest frame exists yet - there is no // renderer to indicate. if (rex::ui::Presenter* presenter = imgui_drawer()->presenter()) { const rex::ui::Presenter::GuestOutputPaintRect rect = presenter->GetLastGuestOutputPaintRect(); if (rect.width <= 0 || rect.height <= 0) { return; } } const bool native = rex::graphics::IsNativeGuestOutputActive(); ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - 10.0f, 10.0f), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); ImGui::SetNextWindowBgAlpha(0.35f); ImGui::Begin("##render_mode_indicator", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav); ImGui::TextColored(native ? ImVec4(0.35f, 1.0f, 0.45f, 1.0f) : ImVec4(1.0f, 0.65f, 0.25f, 1.0f), "%s", native ? "NATIVE" : "EMULATED"); ImGui::End(); } } // namespace skate3