Files
Toastconcern--FrameStudio/src/world_settings.h
T
ToastconcernandClaude Opus 5.5 6d0b4fcd2f v0.2.0: splats, copy/paste, faster homes, seat facing, and a full bug sweep
Frame Studio
- Gaussian splats (PLY, SPZ v2-v4), PLY vertex colours, panorama skies
- Copy, cut, paste and duplicate for objects, splats and world items
- Walkable-area rebake moved off the UI thread; viewport one frame sooner;
  refresh-locked pacing (FS_PACE=timer for the old one)
- Fixed: bundle thumbnail use-after-free crash, auto-save deleting
  environments, saves into scratch copies, edits lost on non-ASCII paths,
  unopenable manifests with inf/nan, bundle save data loss, opening a home
  marking it changed, gizmo NaN, long paths, many malformed-file crashes
- Updater verifies sha256, journals the swap and never leaves the exe missing

Frame Home
- Seats turn you to face their way
- Draw batching (Hand Tutorials up to ~20% less GPU time), cut-out EQUAL pass,
  late head-pose read before submit
- Proper queue-family hand-over to the compositor layer; layer descriptor
  generations and seqlocked slots; structural SPIR-V check
- Fixed: hangs/crashes on damaged .fenv files and shaders, render-pass
  validation error, sync hazard in the foveation smoothing pass, unverified
  update downloads, half-done installs

Tests: tests_loco/env_share/spv_check wired into CMake (FRAME_TESTS) and CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsaXGU27gsrXjDoFtyFJP9
2026-10-25 13:15:07 -05:00

112 lines
6.1 KiB
C++

#pragma once
// A world's own render settings: what that one home runs best at on the Steam Frame, over the headset's settings.json.
// A .fenv carries them in its manifest ("settings" in fenv.json); an APK home in envs/ in "<apk>.settings.json"
// beside it. Every field is optional: unset, the headset's own setting applies. Frame Home applies them while the
// world is shown (resolution, foveation strength and frame rate live; MSAA and filtering by restarting itself, as a
// settings change does); `fenv-tool tune` and Frame Studio's Calculate Best Settings work them out on the headset.
#include "perf_settings.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <string>
struct WorldSettings {
float resolution = 0; // a fixed resolution scale (0.5 .. 2); 0: the headset's (fixed or auto)
int msaa = 0; // 1, 2, 4; 0: the headset's
int anisotropy = 0; // 4, 8, 16; 0: the headset's
float foveation = 0; // foveation strength, 1 gentle .. 3 strong, in between too; 0: the headset's level
int frameRate = -1; // 0 the display's, 1 half; -1: the headset's
std::string tuned; // how they were found ("" when set by hand)
bool empty() const { return resolution <= 0 && msaa <= 0 && anisotropy <= 0 && foveation <= 0 && frameRate < 0; }
bool operator==(const WorldSettings& o) const {
return std::abs(resolution - o.resolution) < 1e-3f && msaa == o.msaa && anisotropy == o.anisotropy &&
std::abs(foveation - o.foveation) < 1e-3f && frameRate == o.frameRate && tuned == o.tuned;
}
bool operator!=(const WorldSettings& o) const { return !(*this == o); }
// The headset's settings with this world's on top.
PerfSettings apply(PerfSettings p) const {
if (resolution > 0) { p.autoResolution = false; p.resolution = resolution; }
if (msaa > 0) p.msaa = msaa;
if (anisotropy > 0) p.anisotropy = anisotropy;
if (foveation > 0) p.foveationLevel = static_cast<int>(std::lround(foveation));
if (frameRate >= 0) p.frameRate = frameRate;
p.clamp();
return p;
}
// The foveation strength to draw with: this world's, else the headset's level.
float foveationStrength(const PerfSettings& headset) const { return foveation > 0 ? foveation : static_cast<float>(headset.foveationLevel); }
void clamp() {
if (!std::isfinite(resolution) || resolution <= 0) resolution = 0;
else resolution = std::clamp(resolution, 0.5f, 2.0f);
msaa = msaa <= 0 ? 0 : msaa >= 4 ? 4 : msaa >= 2 ? 2 : 1;
anisotropy = anisotropy <= 0 ? 0 : anisotropy >= 16 ? 16 : anisotropy >= 8 ? 8 : 4;
if (!std::isfinite(foveation) || foveation <= 0) foveation = 0;
else foveation = std::clamp(foveation, 1.0f, 3.0f);
frameRate = frameRate < 0 ? -1 : std::min(frameRate, 1);
}
// Only the fields that are set ("{}" when none).
std::string toJson() const {
std::string j;
char b[96];
auto add = [&](const char* k, const char* v) { j += std::string(j.empty() ? "" : ", ") + "\"" + k + "\": " + v; };
if (resolution > 0) { std::snprintf(b, sizeof b, "%.2f", resolution); add("resolution", b); }
if (msaa > 0) { std::snprintf(b, sizeof b, "%d", msaa); add("msaa", b); }
if (anisotropy > 0) { std::snprintf(b, sizeof b, "%d", anisotropy); add("anisotropy", b); }
if (foveation > 0) { std::snprintf(b, sizeof b, "%.3f", foveation); add("foveation", b); }
if (frameRate >= 0) { std::snprintf(b, sizeof b, "%d", frameRate); add("frameRate", b); }
if (!tuned.empty()) {
std::string q = "\"";
for (char c : tuned) {
if (c == '"' || c == '\\') { q += '\\'; q += c; }
else if (static_cast<unsigned char>(c) >= 0x20) q += c;
}
add("tuned", (q + "\"").c_str());
}
return "{" + j + "}";
}
bool fromValue(const tinyjson::Value& d) {
if (!d.isObject()) return false;
*this = WorldSettings();
// (a file's numbers: inf / NaN / 1e300 as an int is undefined, and NaN would be written back as invalid JSON)
const auto num = [&](const char* k, double unset) {
const double v = d[k].asFloat();
return std::isfinite(v) ? std::min(1e6, std::max(-1e6, v)) : unset;
};
if (d.has("resolution")) resolution = static_cast<float>(num("resolution", 0));
if (d.has("msaa")) msaa = static_cast<int>(num("msaa", 0));
if (d.has("anisotropy")) anisotropy = static_cast<int>(num("anisotropy", 0));
if (d.has("foveation")) foveation = static_cast<float>(num("foveation", 0));
if (d.has("frameRate")) frameRate = static_cast<int>(num("frameRate", -1));
if (d.has("tuned")) tuned = d["tuned"].asString();
clamp();
return true;
}
bool fromJson(const std::string& j) {
const size_t last = j.find_last_not_of(" \t\r\n");
if (last == std::string::npos || j[last] != '}') return false;
try { return fromValue(tinyjson::parse(j)); } catch (...) { return false; }
}
// "1.40x, 2x MSAA, foveation 1.5" (what is set), or "the headset's settings".
std::string describe() const {
if (empty()) return "the headset's settings";
std::string s;
char b[64];
auto add = [&](const char* t) { s += std::string(s.empty() ? "" : ", ") + t; };
if (resolution > 0) { std::snprintf(b, sizeof b, "resolution %.0f%%", resolution * 100); add(b); }
if (msaa > 0) { std::snprintf(b, sizeof b, "%dx MSAA", msaa); add(b); }
if (anisotropy > 0) { std::snprintf(b, sizeof b, "%dx filtering", anisotropy); add(b); }
if (foveation > 0) { std::snprintf(b, sizeof b, "foveation %.3g", foveation); add(b); }
if (frameRate >= 0) add(frameRate ? "half frame rate" : "full frame rate");
return s;
}
};
// An APK home's settings file, beside it in envs/ (a .fenv keeps them in its manifest instead).
inline std::string worldSettingsPathFor(const std::string& homePath) { return homePath + ".settings.json"; }