Decode HDR-encoded colour ASTC textures, not just linear ones (fixes the Focused vista)

Some environments store colour (sRGB) textures as HDR-encoded ASTC — the Focused vista's sky, water and glow. The
LDR decoder fills those with magenta error texels (the vista showed a huge magenta plane). We already re-decode
linear UNORM textures through the HDR profile for the same reason; do it for colour textures too: when the LDR decode
comes back mostly magenta, re-decode through the HDR profile and sRGB-encode the clamped linear colour so the sRGB
view samples it right (alpha stays linear). Verified by rendering the vista before/after. (A few of the vista's
special detail textures are neither plain LDR nor plain HDR colour and still look speckled; left for a follow-up.)

Also: a GUI-subsystem Windows build now sends the tool modes' log (--snapshot, --panorama, --bench) to the file,
since there's no console for them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ToastconcernandClaude Opus 4.8 committed 2026-10-21 20:19:17 -05:00
1 parent 996c447a55
commit b95fd82647
2 files changed
+29 -1

No files matched your search

+20 -1
View File
@@ -2110,7 +2110,7 @@ void HslRenderer::startWorkers() {
// Linear (UNORM) textures — normals, ORM, masks — may carry HDR-encoded ASTC blocks, which the LDR
// profile turns into magenta error texels (a corrupt ORM made the converted Cascadia's floor a
// mirror). Decode them with the HDR profile, unclamped, then clamp to 8 bits (HSR Studio decodes
// every UNORM ASTC texture through float too). sRGB colour textures keep the LDR path.
// every UNORM ASTC texture through float too). Colour (sRGB) textures start on the LDR path.
bool decoded = false;
if (!job.srgb) {
std::vector<uint16_t> half;
@@ -2125,6 +2125,25 @@ void HslRenderer::startWorkers() {
}
}
if (decoded || astc::decodeASTC(job.src->astc.data() + off, static_cast<u32>(lvl), w, h, job.src->bw, job.src->bh, rgba, false, 1)) {
// A colour texture can be HDR-encoded too (the Focused vista's sky, water and glow): the LDR decode
// then fills it with magenta error texels. Spot that (a lot of exact-magenta texels) and re-decode
// through the HDR profile, sRGB-encoding the clamped linear colour so the sRGB view samples it
// right; alpha is linear either way.
if (job.srgb && !decoded && rgba.size() >= static_cast<size_t>(w) * h * 4) {
size_t mag = 0;
for (size_t q = 0; q + 3 < rgba.size(); q += 4)
if (rgba[q] > 250 && rgba[q + 1] < 8 && rgba[q + 2] > 250) ++mag;
std::vector<uint16_t> half;
if (mag * 4 > static_cast<size_t>(w) * h && // a quarter or more of the texels are the error colour
astc::decodeASTCHalf(job.src->astc.data() + off, static_cast<u32>(lvl), w, h, job.src->bw, job.src->bh, half) &&
half.size() >= static_cast<size_t>(w) * h * 4) {
auto toSrgb = [](float c) { c = std::clamp(c, 0.0f, 1.0f); return c <= 0.0031308f ? c * 12.92f : 1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f; };
for (size_t q = 0; q < rgba.size(); ++q) {
const float v = fromHalf(half[q]);
rgba[q] = static_cast<u8>(std::lround(((q & 3) == 3 ? std::clamp(v, 0.0f, 1.0f) : toSrgb(v)) * 255.0f));
}
}
}
// A texture cooked without mips (a 6000^2 flipbook) arrives full size: brought within the cap here.
while (std::max(w, h) > maxDecodedSide() && rgba.size() >= static_cast<size_t>(w) * h * 4) halveRGBA(rgba, w, h);
// The image and its filled staging buffer are made here too: the frame only records the copy.
+9
View File
@@ -2064,6 +2064,15 @@ int runInstall(bool install) {
int main(int argc, char** argv) {
homeos::logCrashes();
#ifdef _WIN32
// A GUI-subsystem build (no console window) has nowhere for the tool modes (--snapshot, --panorama, --bench) to
// print; send their messages to the log instead. runVr reopens it afresh for the environment itself.
if (!homeos::stderrIsTerminal()) {
std::error_code ec;
fs::create_directories(rootDir() / "logs", ec);
homeos::reopenStderr(rootDir() / "logs" / "frame-home.log", "a");
}
#endif
#ifdef FH_USE_VOLK
if (volkInitialize() != VK_SUCCESS) { std::fprintf(stderr, "frame-home: no Vulkan on this system\n"); return 1; }
#endif