diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fe09f4..db3a0a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/skate3_app_common.cpp b/src/skate3_app_common.cpp index c767263..10b5a7e 100644 --- a/src/skate3_app_common.cpp +++ b/src/skate3_app_common.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(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); diff --git a/src/skate3_app_common.h b/src/skate3_app_common.h index 57bf064..27eb530 100644 --- a/src/skate3_app_common.h +++ b/src/skate3_app_common.h @@ -14,6 +14,8 @@ #include #include +#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 simple_settings_dialog_; std::unique_ptr ultrawide_targets_dialog_; + std::unique_ptr native_debug_dialog_; bool recipe_overlay_installed_ = false; bool big_device_aliases_installed_ = false; std::atomic debug_marker_count_{0}; diff --git a/src/skate3_native_debug_dialog.cpp b/src/skate3_native_debug_dialog.cpp new file mode 100644 index 0000000..8ed32a4 --- /dev/null +++ b/src/skate3_native_debug_dialog.cpp @@ -0,0 +1,184 @@ +#include "skate3_native_debug_dialog.h" + +#include + +#include + +#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 diff --git a/src/skate3_native_debug_dialog.h b/src/skate3_native_debug_dialog.h new file mode 100644 index 0000000..53c3517 --- /dev/null +++ b/src/skate3_native_debug_dialog.h @@ -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 + +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 diff --git a/src/skate3_native_scene.cpp b/src/skate3_native_scene.cpp index f31302a..79987b5 100644 --- a/src/skate3_native_scene.cpp +++ b/src/skate3_native_scene.cpp @@ -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 g_flush_textures{false}; +std::atomic g_flush_meshes{false}; std::atomic g_guest_base{nullptr}; std::atomic 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 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) { - transparent_items.push_back(&item); + 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 diff --git a/src/skate3_native_scene.h b/src/skate3_native_scene.h index 7596835..5f6f2ac 100644 --- a/src/skate3_native_scene.h +++ b/src/skate3_native_scene.h @@ -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