From 1e5054ceac42c97a63428a4adb89395b98890fe1 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 00:59:41 +0100 Subject: Skybox image: apply HDR colour compensation where materials exist Move the HDR decision from texture load to apply_sky_texture, after the sky materials have definitely been enumerated: compensate the pixel buffer (sRGB -> linear, alpha = 1/8) and re-download the procedural texture before assigning it. The earlier load-time check ran before the materials were visible and silently chose the LDR path, so the 8x HDR decode stayed. Also log the compensation explicitly so the chosen path is visible. --- src/world.cpp | 59 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 27 deletions(-) (limited to 'src/world.cpp') diff --git a/src/world.cpp b/src/world.cpp index 03b30a8..092df77 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -387,7 +387,7 @@ int paeth(int a, int b, int c) { } bool load_png_rgba(const char *path, std::vector &pixels, - int &out_w, int &out_h, bool hdr_compensation) { + int &out_w, int &out_h) { FILE *f = fopen(path, "rb"); if (!f) { logf("skybox: cannot open %s", path); @@ -610,24 +610,6 @@ bool load_png_rgba(const char *path, std::vector &pixels, } } - // HDR sky materials use the "sky" shader. It samples the compressed HDR - // texture as rgb *= alpha, multiplies by 8, then converts linear->sRGB for - // display. Cancel the 8x with alpha = 1/8 and feed linear values so the - // final conversion restores the original sRGB image. - if (hdr_compensation) { - for (size_t i = 0; i + 3 < pixels.size(); i += 4) { - float rgb[3]; - for (int c = 0; c < 3; c++) { - float v = (float)pixels[i + (size_t)c] / 255.0f; - rgb[c] = (v <= 0.04045f) ? v / 12.92f - : powf((v + 0.055f) / 1.055f, 2.4f); - pixels[i + (size_t)c] = (unsigned char)(rgb[c] * 255.0f + 0.5f); - } - pixels[i + 3] = 32; // 1/8 * 255 - } - logf("skybox: HDR sky shader, using linear colours with 1/8 alpha"); - } - out_w = (int)w; out_h = (int)h; return true; @@ -692,6 +674,7 @@ SkyRegenerator g_sky_regenerator; void *g_sky_tex = nullptr; bool g_sky_tex_tried = false; bool g_sky_applied = false; +bool g_sky_compensated = false; bool g_sky_reload = false; double g_sky_retry_at = 0.0; bool g_sky_fail_logged = false; @@ -729,6 +712,8 @@ void record_orig(const char *material, const char *var, const char *value, {material, var, value ? value : "", texture}); } +bool sky_uses_hdr_shader(); + int apply_sky_texture() { void *ms = material_system(); if (!ms || !g_sky_tex) @@ -741,6 +726,29 @@ int apply_sky_texture() { int total = count(ms); if (total <= 0 || total > 200000) return 0; + // HDR sky materials use the "sky"/Sky_HDR_DX9 shader, which treats the + // compressed texture as rgb *= alpha, scales by 8, then converts + // linear->sRGB. Cancel the 8x with alpha = 1/8 and feed linear values so + // the final conversion restores the original sRGB image. Decide this here, + // where the materials are definitely loaded, and re-upload if needed. + bool hdr = sky_uses_hdr_shader(); + if (hdr && !g_sky_compensated && !g_sky_pixels.empty()) { + for (size_t i = 0; i + 3 < g_sky_pixels.size(); i += 4) { + for (int c = 0; c < 3; c++) { + float v = (float)g_sky_pixels[i + (size_t)c] / 255.0f; + float lin = (v <= 0.04045f) ? v / 12.92f + : powf((v + 0.055f) / 1.055f, 2.4f); + g_sky_pixels[i + (size_t)c] = (unsigned char)(lin * 255.0f + 0.5f); + } + g_sky_pixels[i + 3] = 32; // 1/8 + } + using SetRegenFn = void (*)(void *, void *); + using DownloadFn = void (*)(void *, void *, int); + vfunc(g_sky_tex, 12)(g_sky_tex, &g_sky_regenerator); + vfunc(g_sky_tex, 13)(g_sky_tex, nullptr, 0); + g_sky_compensated = true; + logf("skybox: HDR sky shader, using linear colours with 1/8 alpha"); + } unsigned short inv = invalid(ms); int seen = 0, hits = 0; for (unsigned short h = first(ms); h != inv; h = next(ms, h)) { @@ -892,8 +900,7 @@ void load_sky_texture() { std::vector pixels; int w = 0, h = 0; const char *path = config_file_cstr("skybox.png"); - bool hdr = sky_uses_hdr_shader(); - if (!load_png_rgba(path, pixels, w, h, hdr)) { + if (!load_png_rgba(path, pixels, w, h)) { if (!g_sky_fail_logged) { g_sky_fail_logged = true; toastf(TOAST_ERROR, "Skybox image missing or unreadable: %s", path); @@ -908,12 +915,9 @@ void load_sky_texture() { // is called on Download() and again on device restore. using CreateProcFn = void *(*)(void *, const char *, const char *, int, int, int, int); - // 0x40 = TEXTUREFLAGS_SRGB: the LDR sky path decodes sRGB from the texture - // flags, the HDR path forces sRGB reads off and does its own conversion. - int tex_flags = 0x10C /* CLAMPS|CLAMPT|NOMIP */ | (hdr ? 0 : 0x40); - g_sky_tex = vfunc(ms, 81)(ms, "hl2hake/skybox", - GROUP_SKYBOX, w, h, 0 /*RGBA8888*/, - tex_flags); + g_sky_tex = vfunc(ms, 81)( + ms, "hl2hake/skybox", GROUP_SKYBOX, w, h, 0 /* RGBA8888 */, + 0x10C /* CLAMPS|CLAMPT|NOMIP */); if (!g_sky_tex) { logf("skybox: CreateProceduralTexture failed for %dx%d", w, h); return; @@ -922,6 +926,7 @@ void load_sky_texture() { using DownloadFn = void (*)(void *, void *, int); vfunc(g_sky_tex, 12)(g_sky_tex, &g_sky_regenerator); vfunc(g_sky_tex, 13)(g_sky_tex, nullptr, 0); + g_sky_compensated = false; g_sky_fail_logged = false; logf("skybox: image %s (%dx%d) uploaded", path, w, h); } -- cgit v1.2.3