From b95fd82647effc6129434a7ccbd560edf6e0f5af Mon Sep 17 00:00:00 2001 From: Toastconcern Date: Wed, 21 Oct 2026 20:19:17 -0500 Subject: [PATCH] Decode HDR-encoded colour ASTC textures, not just linear ones (fixes the Focused vista) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/hsl_renderer.cpp | 21 ++++++++++++++++++++- src/main.cpp | 9 +++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/hsl_renderer.cpp b/src/hsl_renderer.cpp index dcebb2f..f12be6a 100644 --- a/src/hsl_renderer.cpp +++ b/src/hsl_renderer.cpp @@ -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 half; @@ -2125,6 +2125,25 @@ void HslRenderer::startWorkers() { } } if (decoded || astc::decodeASTC(job.src->astc.data() + off, static_cast(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(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 half; + if (mag * 4 > static_cast(w) * h && // a quarter or more of the texels are the error colour + astc::decodeASTCHalf(job.src->astc.data() + off, static_cast(lvl), w, h, job.src->bw, job.src->bh, half) && + half.size() >= static_cast(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(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(w) * h * 4) halveRGBA(rgba, w, h); // The image and its filled staging buffer are made here too: the frame only records the copy. diff --git a/src/main.cpp b/src/main.cpp index 110ea57..27a445f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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