F12 native-render debug menu: live feature toggles for bisecting regressions
This commit is contained in:
parent
7e7b7de777
commit
acd77da02a
7 changed files with 373 additions and 12 deletions
|
|
@ -227,6 +227,7 @@ set(SKATE3_COMMON_SOURCES
|
|||
src/skate3_demo_path.cpp
|
||||
src/skate3_fov.cpp
|
||||
src/skate3_iso_installer.cpp
|
||||
src/skate3_native_debug_dialog.cpp
|
||||
src/skate3_native_render.cpp
|
||||
src/skate3_native_scene.cpp
|
||||
src/skate3_screenshot.cpp
|
||||
|
|
|
|||
|
|
@ -729,6 +729,10 @@ void Skate3BaseApp::OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) {
|
|||
"Write marker to log", [this] {
|
||||
LogUserMarker();
|
||||
});
|
||||
rex::ui::RegisterBind("bind_skate3_native_debug", "F12",
|
||||
"Native render debug menu", [this] {
|
||||
ToggleNativeDebug();
|
||||
});
|
||||
}
|
||||
|
||||
void Skate3BaseApp::OnPostSetup() {
|
||||
|
|
@ -783,9 +787,11 @@ void Skate3BaseApp::OnShutdown() {
|
|||
rex::ui::UnregisterBind("bind_skate3_save_draw_fingerprints");
|
||||
rex::ui::UnregisterBind("bind_skate3_log_debug_marker");
|
||||
rex::ui::UnregisterBind("bind_skate3_log_user_marker");
|
||||
rex::ui::UnregisterBind("bind_skate3_native_debug");
|
||||
ApplyGameplayCursorMode();
|
||||
simple_settings_dialog_.reset();
|
||||
ultrawide_targets_dialog_.reset();
|
||||
native_debug_dialog_.reset();
|
||||
}
|
||||
|
||||
void Skate3BaseApp::ToggleSimpleSettings() {
|
||||
|
|
@ -882,6 +888,19 @@ void Skate3BaseApp::ToggleUltrawideTargets() {
|
|||
ultrawide_targets_dialog_->Show();
|
||||
}
|
||||
|
||||
void Skate3BaseApp::ToggleNativeDebug() {
|
||||
if (!native_debug_dialog_) {
|
||||
native_debug_dialog_ = std::make_unique<skate3::NativeDebugDialog>(imgui_drawer());
|
||||
}
|
||||
if (native_debug_dialog_->visible()) {
|
||||
native_debug_dialog_->Hide();
|
||||
ApplyGameplayCursorMode();
|
||||
} else {
|
||||
ApplySettingsCursorMode();
|
||||
native_debug_dialog_->Show();
|
||||
}
|
||||
}
|
||||
|
||||
void Skate3BaseApp::ApplySettingsCursorMode() {
|
||||
if (window()) {
|
||||
window()->SetCursorVisibility(rex::ui::Window::CursorVisibility::kVisible);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
#include <rex/ui/overlay/simple_settings_overlay.h>
|
||||
#include <rex/ui/overlay/ultrawide_targets_overlay.h>
|
||||
|
||||
#include "skate3_native_debug_dialog.h"
|
||||
|
||||
namespace rex::ui {
|
||||
class ImGuiDrawer;
|
||||
}
|
||||
|
|
@ -39,6 +41,7 @@ class Skate3BaseApp : public rex::ReXApp {
|
|||
void InstallDlcPackages();
|
||||
void ToggleSimpleSettings();
|
||||
void ToggleUltrawideTargets();
|
||||
void ToggleNativeDebug();
|
||||
void ApplySettingsCursorMode();
|
||||
void ApplyGameplayCursorMode();
|
||||
void RestartGame();
|
||||
|
|
@ -58,6 +61,7 @@ class Skate3BaseApp : public rex::ReXApp {
|
|||
std::filesystem::path profiles_path_;
|
||||
std::unique_ptr<rex::ui::SimpleSettingsDialog> simple_settings_dialog_;
|
||||
std::unique_ptr<rex::ui::UltrawideTargetsDialog> ultrawide_targets_dialog_;
|
||||
std::unique_ptr<skate3::NativeDebugDialog> native_debug_dialog_;
|
||||
bool recipe_overlay_installed_ = false;
|
||||
bool big_device_aliases_installed_ = false;
|
||||
std::atomic<uint32_t> debug_marker_count_{0};
|
||||
|
|
|
|||
184
src/skate3_native_debug_dialog.cpp
Normal file
184
src/skate3_native_debug_dialog.cpp
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
#include "skate3_native_debug_dialog.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
|
||||
#include "skate3_native_scene.h"
|
||||
|
||||
// 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_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_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);
|
||||
// SDK: emulated-draw suppression while the native output is active.
|
||||
REXCVAR_DECLARE(bool, native_render_suppress_emulated_draws);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // 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;
|
||||
}
|
||||
|
||||
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"));
|
||||
|
||||
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_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)"));
|
||||
|
||||
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("Caches (lightmap-era plumbing)");
|
||||
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();
|
||||
}
|
||||
ImGui::TextDisabled("MSAA + shadow tile size are restart-only (skate3.toml).");
|
||||
|
||||
ImGui::End();
|
||||
if (!open) {
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace skate3
|
||||
27
src/skate3_native_debug_dialog.h
Normal file
27
src/skate3_native_debug_dialog.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#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.
|
||||
|
||||
#include <rex/ui/imgui_dialog.h>
|
||||
|
||||
namespace skate3 {
|
||||
|
||||
class NativeDebugDialog final : public rex::ui::ImGuiDialog {
|
||||
public:
|
||||
explicit NativeDebugDialog(rex::ui::ImGuiDrawer* drawer) : ImGuiDialog(drawer) {}
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
void Toggle();
|
||||
bool visible() const { return visible_; }
|
||||
|
||||
protected:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
private:
|
||||
bool visible_ = false;
|
||||
};
|
||||
|
||||
} // namespace skate3
|
||||
|
|
@ -107,6 +107,38 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_shadow_tile, 1024, "Skate 3",
|
|||
"matches the edge crispness players currently see.")
|
||||
.range(256, 4096)
|
||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
// Fine-grained feature gates for the F12 native-render debug dialog: each
|
||||
// isolates one subsystem so regressions (flicker, wrong shading) can be
|
||||
// bisected live without rebuilds. All hot-reload, default on.
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_macro, true, "Skate 3",
|
||||
"Apply the macrooverlay grime/crack multiply on world materials")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_decals, true, "Skate 3",
|
||||
"Composite environment.decal art (graffiti/paint) over base diffuse")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_transparents, true, "Skate 3",
|
||||
"Draw environment.transparent items (mist/glass/fences) in the "
|
||||
"alpha-blended sub-pass")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_world_items, true, "Skate 3",
|
||||
"Publish world sort-list items (static geometry)")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_dynamic_items, true, "Skate 3",
|
||||
"Publish dynamic entities (characters, props, cloth)")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_tex_revalidate, true, "Skate 3",
|
||||
"Re-fingerprint cached texture payloads every 16 frames and "
|
||||
"re-decode on change (heals late-composed lightmap pages)")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_mesh_revalidate, true, "Skate 3",
|
||||
"Re-decode cached meshes when their payload fingerprint changes "
|
||||
"(streaming arena reuse; also picks up CPU-animated buffers)")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_tex_mips, true, "Skate 3",
|
||||
"Upload guest texture MIP CHAINS (off = mip 0 only; distant "
|
||||
"surfaces alias but mip-related artifacts disappear). Flush the "
|
||||
"texture cache after toggling.")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
// Hook layer master switch, defined in skate3_native_render.cpp. The runtime
|
||||
// toggle refuses to flip the scene on without it: the hooks that feed the
|
||||
|
|
@ -134,6 +166,11 @@ constexpr uint32_t kViewCamViewProj = 0xA0;
|
|||
std::mutex g_scene_mutex;
|
||||
FrameScene g_scene;
|
||||
uint64_t g_generation = 0;
|
||||
// Debug-dialog cache flushes: consumed at the top of RenderScene so texture/
|
||||
// mesh-affecting toggles (mip chains, 565 fixes, ...) take effect immediately
|
||||
// instead of only for newly streamed content.
|
||||
std::atomic<bool> g_flush_textures{false};
|
||||
std::atomic<bool> g_flush_meshes{false};
|
||||
std::atomic<uint8_t*> g_guest_base{nullptr};
|
||||
std::atomic<uint64_t> g_frames_rendered{0};
|
||||
|
||||
|
|
@ -773,6 +810,7 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
|||
item.hair = false;
|
||||
item.ropa = false;
|
||||
item.decal = false;
|
||||
item.decal_tileable = false;
|
||||
item.transparent = false;
|
||||
item.unlit = false;
|
||||
item.tint[0] = item.tint[1] = item.tint[2] = item.tint[3] = 0.0f;
|
||||
|
|
@ -815,8 +853,8 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
|||
// variant (flag-row-switched skinned/rigid, see CaptureSkinnedState).
|
||||
const uint32_t s = REX_LOAD_U32(chan + 0x18);
|
||||
if (GuestReadableApprox(base, s)) {
|
||||
char mat_name[24] = {};
|
||||
for (int k = 0; k < 23; ++k) {
|
||||
char mat_name[28] = {};
|
||||
for (int k = 0; k < 27; ++k) {
|
||||
mat_name[k] = char(REX_LOAD_U8(s + k));
|
||||
if (mat_name[k] == '\0') break;
|
||||
}
|
||||
|
|
@ -825,7 +863,13 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
|||
item.ropa = std::memcmp(mat_name, "character.cloth_ropa", 21) == 0;
|
||||
// environment.decal / environment.decal_tileable: graffiti and
|
||||
// painted-branding overlay meshes (see DrawItem::decal).
|
||||
// Tileable art WRAPS (rock/cliff faces tile the art across the
|
||||
// whole surface, uv spans many periods; clamping stretches the
|
||||
// border texels into giant streaks); single-placement decal art
|
||||
// CLAMPS (wrap tiled the graffiti across the plaza).
|
||||
item.decal = std::memcmp(mat_name, "environment.decal", 17) == 0;
|
||||
item.decal_tileable =
|
||||
std::memcmp(mat_name, "environment.decal_tileable", 26) == 0;
|
||||
// environment.transparent: alpha-blended world geometry (mist
|
||||
// sheets, glass, fences); see DrawItem::transparent.
|
||||
item.transparent =
|
||||
|
|
@ -935,6 +979,9 @@ bool BuildItemGeometry(uint8_t* base, uint32_t ctx, DrawItem& item) {
|
|||
|
||||
bool Enabled() { return SceneEnabled(); }
|
||||
|
||||
void FlushTextureCache() { g_flush_textures.store(true, std::memory_order_relaxed); }
|
||||
void FlushMeshCache() { g_flush_meshes.store(true, std::memory_order_relaxed); }
|
||||
|
||||
bool ToggleSceneEnabled() {
|
||||
if (!REXCVAR_GET(skate3_native_render)) {
|
||||
REXLOG_WARN(
|
||||
|
|
@ -2173,6 +2220,9 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
|
|||
.fetch_add(1, std::memory_order_relaxed);
|
||||
continue;
|
||||
}
|
||||
if (!REXCVAR_GET(skate3_native_render_scene_dynamic_items)) {
|
||||
continue;
|
||||
}
|
||||
auto [slot, inserted] = dyn_slot.try_emplace(r.a, scene.items.size());
|
||||
if (inserted) {
|
||||
scene.items.push_back(cand);
|
||||
|
|
@ -2184,6 +2234,9 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
|
|||
if (!seen.insert(r.a).second) {
|
||||
continue;
|
||||
}
|
||||
if (!REXCVAR_GET(skate3_native_render_scene_world_items)) {
|
||||
continue;
|
||||
}
|
||||
DrawItem item;
|
||||
if (BuildItemGeometry(base, r.a, item)) {
|
||||
if (item.skinned) {
|
||||
|
|
@ -2755,7 +2808,12 @@ float4 ps_main(VSOut i) : SV_Target {
|
|||
// tiling uv0 repeats it: "Stereo Stereo Stereo"; the fmt-26 second
|
||||
// element is the lightmap unwrap, not the decal's).
|
||||
if (overlay.w > 0.0) {
|
||||
float4 dk = decal_art.Sample(smp_clamp, i.uv3);
|
||||
// overlay.w == 2 marks environment.decal_tileable: the art tiles across
|
||||
// the surface (rock/cliff faces) and must WRAP; clamp stretched the
|
||||
// border texels into giant streaks. Single placements clamp (their
|
||||
// transparent border keeps everything outside the placement clear).
|
||||
float4 dk = overlay.w > 1.5 ? decal_art.Sample(smp, i.uv3)
|
||||
: decal_art.Sample(smp_clamp, i.uv3);
|
||||
albedo.rgb = lerp(albedo.rgb, dk.rgb, dk.a);
|
||||
}
|
||||
// tint.r > 0 marks items with a lightmap bound (2x baked lighting);
|
||||
|
|
@ -3311,7 +3369,8 @@ bool EnsureGuestTextureFromWords(const NativeGuestOutputRenderContext& context,
|
|||
// alignment holds on every level.
|
||||
const bool pow2 = (width & (width - 1)) == 0 && (height & (height - 1)) == 0;
|
||||
uint32_t mip_count = 1;
|
||||
if (pow2 && info.memory.mip_address != 0) {
|
||||
if (pow2 && info.memory.mip_address != 0 &&
|
||||
REXCVAR_GET(skate3_native_render_scene_tex_mips)) {
|
||||
const uint32_t avail = std::min(info.mip_levels(), info.GetMaxMipLevels());
|
||||
while (mip_count < avail && (width >> mip_count) >= 4 && (height >> mip_count) >= 4) {
|
||||
uint32_t ox = 0, oy = 0;
|
||||
|
|
@ -4274,6 +4333,33 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
});
|
||||
}
|
||||
|
||||
// Debug-dialog cache flushes: retire every cached decode (freed once the
|
||||
// GPU is done with the current submission) so hot-toggled decode settings
|
||||
// rebuild the world with the new rules this frame.
|
||||
if (g_flush_textures.exchange(false, std::memory_order_relaxed)) {
|
||||
const uint64_t submission = command_processor->GetCurrentSubmission();
|
||||
for (auto& [key, t] : g_r.textures) {
|
||||
if (t.texture) g_r.retired.emplace_back(t.texture, submission);
|
||||
if (t.upload) g_r.retired.emplace_back(t.upload, submission);
|
||||
}
|
||||
g_r.textures.clear();
|
||||
for (auto& [key, t] : g_r.textures_2d) {
|
||||
if (t.texture) g_r.retired.emplace_back(t.texture, submission);
|
||||
if (t.upload) g_r.retired.emplace_back(t.upload, submission);
|
||||
}
|
||||
g_r.textures_2d.clear();
|
||||
REXLOG_INFO("native-scene: texture cache flushed (debug dialog)");
|
||||
}
|
||||
if (g_flush_meshes.exchange(false, std::memory_order_relaxed)) {
|
||||
const uint64_t submission = command_processor->GetCurrentSubmission();
|
||||
for (auto& [key, m] : g_r.meshes) {
|
||||
if (m.vb) g_r.retired.emplace_back(m.vb, submission);
|
||||
if (m.ib) g_r.retired.emplace_back(m.ib, submission);
|
||||
}
|
||||
g_r.meshes.clear();
|
||||
REXLOG_INFO("native-scene: mesh cache flushed (debug dialog)");
|
||||
}
|
||||
|
||||
// Reset this frame's bone ring region (shared by the shadow casters and
|
||||
// the main pass: the shadow pass allocates first, the main pass appends).
|
||||
const uint64_t frame_number = g_frames_rendered.load(std::memory_order_relaxed);
|
||||
|
|
@ -4555,7 +4641,8 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
// holes in the world).
|
||||
const auto draw_item = [&](const DrawItem& item) {
|
||||
auto it = g_r.meshes.find(item.mesh);
|
||||
if (it != g_r.meshes.end() && it->second.fingerprint != item.fingerprint) {
|
||||
if (it != g_r.meshes.end() && it->second.fingerprint != item.fingerprint &&
|
||||
REXCVAR_GET(skate3_native_render_scene_mesh_revalidate)) {
|
||||
const uint64_t submission = command_processor->GetCurrentSubmission();
|
||||
g_r.retired.emplace_back(it->second.vb, submission);
|
||||
g_r.retired.emplace_back(it->second.ib, submission);
|
||||
|
|
@ -4607,14 +4694,32 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
// Re-decode when the sampled payload changed; keep the old decode
|
||||
// when the payload became unreadable (mip streamed out at range).
|
||||
bool payload_changed = false;
|
||||
if (tit->second.valid && frame_number >= tit->second.recheck_frame) {
|
||||
if (tit->second.valid && frame_number >= tit->second.recheck_frame &&
|
||||
REXCVAR_GET(skate3_native_render_scene_tex_revalidate)) {
|
||||
tit->second.recheck_frame = frame_number + 16;
|
||||
const uint64_t fp = SamplePayloadFingerprint(
|
||||
base, tit->second.payload_addr, tit->second.payload_size);
|
||||
payload_changed = fp != 0 && fp != tit->second.payload_fp;
|
||||
}
|
||||
if (retry_failed || payload_changed ||
|
||||
std::memcmp(live, tit->second.fetch_words, sizeof(live)) != 0) {
|
||||
const bool words_changed =
|
||||
std::memcmp(live, tit->second.fetch_words, sizeof(live)) != 0;
|
||||
if (retry_failed || payload_changed || words_changed) {
|
||||
// Re-decode churn diagnostic: repeated fetch-word or payload
|
||||
// changes on one object = streaming oscillation, visible as
|
||||
// texture flicker on the affected meshes.
|
||||
static std::atomic<uint32_t> s_redecode_logs{0};
|
||||
if (s_redecode_logs.fetch_add(1) < 256) {
|
||||
REXLOG_INFO(
|
||||
"native-scene: texture re-decode obj={:08X} reason={}{}{} "
|
||||
"old=[{:08X} {:08X} {:08X} {:08X} {:08X} {:08X}] new=[{:08X} {:08X} "
|
||||
"{:08X} {:08X} {:08X} {:08X}]",
|
||||
tex_ptr, words_changed ? "words" : "", payload_changed ? "payload" : "",
|
||||
retry_failed ? "retry" : "", tit->second.fetch_words[0],
|
||||
tit->second.fetch_words[1], tit->second.fetch_words[2],
|
||||
tit->second.fetch_words[3], tit->second.fetch_words[4],
|
||||
tit->second.fetch_words[5], live[0], live[1], live[2], live[3], live[4],
|
||||
live[5]);
|
||||
}
|
||||
const uint64_t submission = command_processor->GetCurrentSubmission();
|
||||
if (tit->second.texture) g_r.retired.emplace_back(tit->second.texture, submission);
|
||||
if (tit->second.upload) g_r.retired.emplace_back(tit->second.upload, submission);
|
||||
|
|
@ -4732,15 +4837,21 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
// binding only the art dropped the macro multiply there; alternating
|
||||
// plaza sections rendered ~1.4x too bright (the ground checkerboard).
|
||||
const GuestTexture* macro_tex =
|
||||
item.macro_tex != 0 ? resolve_texture(item.macro_tex) : &g_r.white;
|
||||
const GuestTexture* decal_tex = item.decal && item.decal_art != 0
|
||||
item.macro_tex != 0 && REXCVAR_GET(skate3_native_render_scene_macro)
|
||||
? resolve_texture(item.macro_tex)
|
||||
: &g_r.white;
|
||||
const GuestTexture* decal_tex = item.decal && item.decal_art != 0 &&
|
||||
REXCVAR_GET(skate3_native_render_scene_decals)
|
||||
? resolve_texture(item.decal_art)
|
||||
: &g_r.white;
|
||||
const bool is_decal = decal_tex != &g_r.white;
|
||||
constants[44] = item.macro_scale;
|
||||
constants[45] = item.macro_opacity;
|
||||
constants[46] = macro_tex != &g_r.white ? 1.0f : 0.0f;
|
||||
constants[47] = is_decal ? 1.0f : 0.0f;
|
||||
// overlay.w: 1 = single-placement decal (art clamps), 2 = tileable
|
||||
// decal (art wraps; clamping a many-period uv range stretched the
|
||||
// border texels into the giant cliff-face streaks).
|
||||
constants[47] = is_decal ? (item.decal_tileable ? 2.0f : 1.0f) : 0.0f;
|
||||
// misc.x: alpha-blended sub-pass item (transparentenvironment shading);
|
||||
// fog rides in misc.yzw (ramp) and the mat_tint row (color), unused by
|
||||
// transparent items otherwise (root-signature DWORD budget).
|
||||
|
|
@ -4802,7 +4913,9 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
break;
|
||||
}
|
||||
if (item.transparent && debug_mode == 0) {
|
||||
if (REXCVAR_GET(skate3_native_render_scene_transparents)) {
|
||||
transparent_items.push_back(&item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
draw_item(item);
|
||||
|
|
@ -5045,6 +5158,8 @@ void Install() {
|
|||
|
||||
namespace skate3::native_scene {
|
||||
void Install() {}
|
||||
void FlushTextureCache() {}
|
||||
void FlushMeshCache() {}
|
||||
} // namespace skate3::native_scene
|
||||
|
||||
#endif // REX_HAS_D3D12
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ struct DrawItem {
|
|||
// rendering them alpha-blended punches holes in walls). Without the
|
||||
// composite the paint is missing (the no-graffiti bug).
|
||||
bool decal;
|
||||
// environment.decal_tileable: the decal art TILES across the surface
|
||||
// (rock/cliff faces) and must sample with WRAP; single-placement decal
|
||||
// art samples with CLAMP (its transparent border keeps the area outside
|
||||
// the placement clear).
|
||||
bool decal_tileable;
|
||||
uint32_t decal_art; // `decal` channel texture (0 = none)
|
||||
// AttribulatorMaterialName starts "environment.transparent": mist/cloud
|
||||
// sheets, glass, fences, vines. Alpha-BLENDED in a sub-pass after all
|
||||
|
|
@ -234,4 +239,10 @@ void Install();
|
|||
// layer was not enabled at boot.
|
||||
bool ToggleSceneEnabled();
|
||||
|
||||
// Debug-dialog cache flushes (F12 native-render debug menu): retire every
|
||||
// cached GPU texture / mesh decode so hot-toggled decode settings (mip
|
||||
// chains, revalidation, ...) rebuild immediately.
|
||||
void FlushTextureCache();
|
||||
void FlushMeshCache();
|
||||
|
||||
} // namespace skate3::native_scene
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue