F12 native-render debug menu: live feature toggles for bisecting regressions

This commit is contained in:
mchughalex 2026-07-07 01:24:07 +01:00
parent 7e7b7de777
commit acd77da02a
7 changed files with 373 additions and 12 deletions

View file

@ -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

View file

@ -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);

View file

@ -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};

View 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

View 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

View file

@ -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) {
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

View file

@ -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