Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/Features/RenderDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,14 @@ void RenderDoc::DrawSettings()
globals::state->frameAnnotations = globals::state->useFrameAnnotations;
}
}
Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::enableCapture);

if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Enable RenderDoc frame capture for providing debug captures to the Open Shaders team (or upstream Community Shaders for upstream-relevant issues).");
ImGui::Text("Enabling capture will force-enable frame annotations for easier debugging and will restore the previous setting when disabled.");
}
// enableCapture is restart-gated (renderdoc.dll only injects at boot). The
// helper renders the tooltip first so it attaches to the checkbox, then the
// pending banner below it -- the previous ordering drew the banner between
// the checkbox and the tooltip, so a pending banner would steal the hover.
Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::enableCapture, [] {
Comment thread
alandtse marked this conversation as resolved.
ImGui::TextUnformatted("Enable RenderDoc frame capture for providing debug captures to the Open Shaders team (or upstream Community Shaders for upstream-relevant issues).");
ImGui::TextUnformatted("Enabling capture will force-enable frame annotations for easier debugging and will restore the previous setting when disabled.");
});

// The rest of the UI renders only when capture is active
bool renderDocCaptureEnabled = settings.enableCapture;
Expand Down
33 changes: 23 additions & 10 deletions src/Features/Upscaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,16 +329,20 @@ void Upscaling::DrawSettings()
ImGui::Checkbox("Render engine at upscaled resolution", &settings.renderAtUpscaleRes);
if (!methodSupportsPerf)
ImGui::EndDisabled();
// Hover tooltip always renders (so users learn what the option does
// even when greyed out). The pending-restart banner only fires when
// DLSS is the active upscaler -- the feature can't take effect
// otherwise, so a "pending restart" hint there would mislead.
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text(
"When enabled, the engine pipeline allocates render targets at the upscaled-render\n"
"resolution instead of the HMD display resolution. The upscaler (DLSS or FSR) writes\n"
"its output to a private DisplayRes texture. Substantial VRAM and bandwidth savings,\n"
"especially at high HMD resolutions.\n"
"\n"
"Requires DLSS or FSR. Restart required to enable/disable. Method and Upscale\n"
"Preset changes also require a restart while this is active; sharpness / model preset\n"
"/ Reflex remain live.");
"Requires DLSS or FSR. Toggling this option requires a game restart to take effect.\n"
"While active, Method and Upscale Preset changes also require a restart;\n"
"sharpness / model preset / Reflex remain live.");
}
if (!methodSupportsPerf && settings.renderAtUpscaleRes)
Util::Text::Disabled("Render-at-upscaled-resolution requires DLSS or FSR — switch upscaler Method to activate.");
Expand Down Expand Up @@ -372,7 +376,10 @@ void Upscaling::DrawSettings()
bool fgEnabled = settings.frameGenerationMode != 0;
if (ImGui::Checkbox("Frame Generation", &fgEnabled))
settings.frameGenerationMode = fgEnabled ? 1 : 0;
Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::frameGenerationMode);
Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::frameGenerationMode,
"Interpolate real frames with generated ones for a smoother experience. Uses AMD FSR Frame\n"
"Generation. Requires a D3D11-to-D3D12 proxy swapchain which can introduce compatibility\n"
"issues; in particular, frame generation works only in windowed mode.");

if (!frameGenerationDx12PathActive)
ImGui::BeginDisabled();
Expand All @@ -388,7 +395,10 @@ void Upscaling::DrawSettings()
bool fgForce = settings.frameGenerationForceEnable != 0;
if (ImGui::Checkbox("Force Enable Frame Generation", &fgForce))
settings.frameGenerationForceEnable = fgForce ? 1 : 0;
Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::frameGenerationForceEnable);
Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::frameGenerationForceEnable,
"Bypass the high-refresh-rate monitor check so Frame Generation can run on lower-Hz\n"
"displays. Useful for laptops and older monitors at the cost of less headroom for the\n"
"generated frames.");

ImGui::Checkbox("Frame Generation in Menus", &settings.frameGenerationAllowInMenus);
if (auto _tt = Util::HoverTooltipWrapper()) {
Expand Down Expand Up @@ -499,14 +509,17 @@ void Upscaling::DrawSettings()
if (ImGui::TreeNodeEx("Backend Diagnostics")) {
// Streamline log level selection
const char* logLevels[] = { "Off", "Default", "Verbose" };
int logLevelIdx = static_cast<int>(settings.streamlineLogLevel);
// Clamp before use: streamlineLogLevel is JSON-persisted and could be out
// of range (or a value that overflows the int cast) on a stale or
// hand-edited config; an unclamped value would index logLevels OOB.
int logLevelIdx = std::clamp(static_cast<int>(settings.streamlineLogLevel),
0, IM_ARRAYSIZE(logLevels) - 1);
if (ImGui::Combo("Streamline Logging", &logLevelIdx, logLevels, IM_ARRAYSIZE(logLevels))) {
settings.streamlineLogLevel = static_cast<uint>(logLevelIdx);
}
Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::streamlineLogLevel);
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Streamline logging controls the verbosity of NVIDIA Streamline backend logs. Useful for debugging issues with DLSS/DLSS-G.");
}
Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::streamlineLogLevel,
"Verbosity of the NVIDIA Streamline backend logs. Useful for debugging issues with DLSS / "
"DLSS-G.");

// VR Debug visualization -- per-eye buffers and native inputs
if (globals::game::isVR) {
Expand Down
8 changes: 6 additions & 2 deletions src/Features/VolumetricLighting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,22 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(

void VolumetricLighting::DrawSettings()
{
// VR pre-allocates VL render targets at boot, so a runtime toggle can't
// resize them -- gate only in VR. Non-VR resizes live.
if (ImGui::Checkbox("Enable Volumetric Lighting in Exteriors", &settings.ExteriorEnabled))
SetupVL();
if (globals::game::isVR)
Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::ExteriorEnabled);
Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::ExteriorEnabled,
"Volumetric god-rays / fog scattering in exterior cells.");

if (settings.ExteriorEnabled)
DrawVolumetricLightingSettings(settings.ExteriorQuality, settings.ExteriorCustomSize, false, !inInterior);

if (ImGui::Checkbox("Enable Volumetric Lighting in Interiors", &settings.InteriorEnabled))
SetupVL();
if (globals::game::isVR)
Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::InteriorEnabled);
Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::InteriorEnabled,
"Volumetric god-rays / fog scattering in interior cells.");

if (settings.InteriorEnabled)
DrawVolumetricLightingSettings(settings.InteriorQuality, settings.InteriorCustomSize, true, inInterior);
Expand Down
18 changes: 18 additions & 0 deletions src/Utils/BootSnapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,17 @@ namespace Util::Settings
return *reinterpret_cast<const T*>(reinterpret_cast<const std::byte*>(&bootCopy_) + offset);
}

// T's bytes are memcmp'd, so a padded struct could false-positive on
// compare. All registered restart fields are bool/uint/float/enum (no
// padding); constrain with has_unique_object_representations_v if that changes.
template <typename T>
bool HasPendingChange(const SettingsT& live, T SettingsT::* member) const noexcept
{
// SettingsT may hold std::string, but a registered restart field must be
// trivially copyable -- memcmp on a std::string compares control blocks, not text.
static_assert(std::is_trivially_copyable_v<T>,
"BootSnapshot::HasPendingChange requires a trivially-copyable field type "
"(memcmp on non-trivial types is not a meaningful equality check).");
if (!latched_) {
return false;
}
Expand All @@ -115,6 +123,16 @@ namespace Util::Settings
if (!latched_ || !field.jsonKey) {
return false;
}
// Defensive bounds check on the runtime-supplied descriptor: a
// malformed RestartFieldInfo (size zero, offset past the end, or
// offset+size extending beyond sizeof(SettingsT)) would otherwise
// drive memcmp out of bounds. Subtract instead of add to avoid
// overflow when offset+size would wrap size_t.
if (field.size == 0 ||
field.offset > sizeof(SettingsT) ||
field.size > sizeof(SettingsT) - field.offset) {
return false;
}
return std::memcmp(reinterpret_cast<const std::byte*>(&bootCopy_) + field.offset,
reinterpret_cast<const std::byte*>(&live) + field.offset,
field.size) != 0;
Expand Down
7 changes: 6 additions & 1 deletion src/Utils/UI.h
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,12 @@ namespace Util
}

template <typename SettingsT, typename T, typename Body>
requires std::invocable<Body>
// body is invoked as an lvalue (`body()` below). Constrain on
// `Body&` so a non-const-callable, move-only, or otherwise
// lvalue-only invocable is not falsely rejected -- the prior
// `std::invocable<Body>` form tested invocation on a forwarded-as
// expression that may decay to rvalue and reject lvalue-only types.
requires std::invocable<Body&>
inline void RestartGatedAnnotate(const Util::Settings::BootSnapshot<SettingsT>& snapshot,
const SettingsT& live,
T SettingsT::* field,
Expand Down
Loading