summaryrefslogtreecommitdiffstats
path: root/src/world.cpp
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 00:59:41 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 00:59:41 +0100
commit1e5054ceac42c97a63428a4adb89395b98890fe1 (patch)
tree65ab35f820d4e9c3abccffd47c78b8c279cdb496 /src/world.cpp
parentSkybox image: recognise Sky_HDR_DX9 as the HDR sky shader (diff)
downloadhl2h-1e5054ceac42c97a63428a4adb89395b98890fe1.tar.gz
hl2h-1e5054ceac42c97a63428a4adb89395b98890fe1.zip
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.
Diffstat (limited to '')
-rw-r--r--src/world.cpp59
1 files changed, 32 insertions, 27 deletions
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) {
387} 387}
388 388
389bool load_png_rgba(const char *path, std::vector<unsigned char> &pixels, 389bool load_png_rgba(const char *path, std::vector<unsigned char> &pixels,
390 int &out_w, int &out_h, bool hdr_compensation) { 390 int &out_w, int &out_h) {
391 FILE *f = fopen(path, "rb"); 391 FILE *f = fopen(path, "rb");
392 if (!f) { 392 if (!f) {
393 logf("skybox: cannot open %s", path); 393 logf("skybox: cannot open %s", path);
@@ -610,24 +610,6 @@ bool load_png_rgba(const char *path, std::vector<unsigned char> &pixels,
610 } 610 }
611 } 611 }
612 612
613 // HDR sky materials use the "sky" shader. It samples the compressed HDR
614 // texture as rgb *= alpha, multiplies by 8, then converts linear->sRGB for
615 // display. Cancel the 8x with alpha = 1/8 and feed linear values so the
616 // final conversion restores the original sRGB image.
617 if (hdr_compensation) {
618 for (size_t i = 0; i + 3 < pixels.size(); i += 4) {
619 float rgb[3];
620 for (int c = 0; c < 3; c++) {
621 float v = (float)pixels[i + (size_t)c] / 255.0f;
622 rgb[c] = (v <= 0.04045f) ? v / 12.92f
623 : powf((v + 0.055f) / 1.055f, 2.4f);
624 pixels[i + (size_t)c] = (unsigned char)(rgb[c] * 255.0f + 0.5f);
625 }
626 pixels[i + 3] = 32; // 1/8 * 255
627 }
628 logf("skybox: HDR sky shader, using linear colours with 1/8 alpha");
629 }
630
631 out_w = (int)w; 613 out_w = (int)w;
632 out_h = (int)h; 614 out_h = (int)h;
633 return true; 615 return true;
@@ -692,6 +674,7 @@ SkyRegenerator g_sky_regenerator;
692void *g_sky_tex = nullptr; 674void *g_sky_tex = nullptr;
693bool g_sky_tex_tried = false; 675bool g_sky_tex_tried = false;
694bool g_sky_applied = false; 676bool g_sky_applied = false;
677bool g_sky_compensated = false;
695bool g_sky_reload = false; 678bool g_sky_reload = false;
696double g_sky_retry_at = 0.0; 679double g_sky_retry_at = 0.0;
697bool g_sky_fail_logged = false; 680bool g_sky_fail_logged = false;
@@ -729,6 +712,8 @@ void record_orig(const char *material, const char *var, const char *value,
729 {material, var, value ? value : "", texture}); 712 {material, var, value ? value : "", texture});
730} 713}
731 714
715bool sky_uses_hdr_shader();
716
732int apply_sky_texture() { 717int apply_sky_texture() {
733 void *ms = material_system(); 718 void *ms = material_system();
734 if (!ms || !g_sky_tex) 719 if (!ms || !g_sky_tex)
@@ -741,6 +726,29 @@ int apply_sky_texture() {
741 int total = count(ms); 726 int total = count(ms);
742 if (total <= 0 || total > 200000) 727 if (total <= 0 || total > 200000)
743 return 0; 728 return 0;
729 // HDR sky materials use the "sky"/Sky_HDR_DX9 shader, which treats the
730 // compressed texture as rgb *= alpha, scales by 8, then converts
731 // linear->sRGB. Cancel the 8x with alpha = 1/8 and feed linear values so
732 // the final conversion restores the original sRGB image. Decide this here,
733 // where the materials are definitely loaded, and re-upload if needed.
734 bool hdr = sky_uses_hdr_shader();
735 if (hdr && !g_sky_compensated && !g_sky_pixels.empty()) {
736 for (size_t i = 0; i + 3 < g_sky_pixels.size(); i += 4) {
737 for (int c = 0; c < 3; c++) {
738 float v = (float)g_sky_pixels[i + (size_t)c] / 255.0f;
739 float lin = (v <= 0.04045f) ? v / 12.92f
740 : powf((v + 0.055f) / 1.055f, 2.4f);
741 g_sky_pixels[i + (size_t)c] = (unsigned char)(lin * 255.0f + 0.5f);
742 }
743 g_sky_pixels[i + 3] = 32; // 1/8
744 }
745 using SetRegenFn = void (*)(void *, void *);
746 using DownloadFn = void (*)(void *, void *, int);
747 vfunc<SetRegenFn>(g_sky_tex, 12)(g_sky_tex, &g_sky_regenerator);
748 vfunc<DownloadFn>(g_sky_tex, 13)(g_sky_tex, nullptr, 0);
749 g_sky_compensated = true;
750 logf("skybox: HDR sky shader, using linear colours with 1/8 alpha");
751 }
744 unsigned short inv = invalid(ms); 752 unsigned short inv = invalid(ms);
745 int seen = 0, hits = 0; 753 int seen = 0, hits = 0;
746 for (unsigned short h = first(ms); h != inv; h = next(ms, h)) { 754 for (unsigned short h = first(ms); h != inv; h = next(ms, h)) {
@@ -892,8 +900,7 @@ void load_sky_texture() {
892 std::vector<unsigned char> pixels; 900 std::vector<unsigned char> pixels;
893 int w = 0, h = 0; 901 int w = 0, h = 0;
894 const char *path = config_file_cstr("skybox.png"); 902 const char *path = config_file_cstr("skybox.png");
895 bool hdr = sky_uses_hdr_shader(); 903 if (!load_png_rgba(path, pixels, w, h)) {
896 if (!load_png_rgba(path, pixels, w, h, hdr)) {
897 if (!g_sky_fail_logged) { 904 if (!g_sky_fail_logged) {
898 g_sky_fail_logged = true; 905 g_sky_fail_logged = true;
899 toastf(TOAST_ERROR, "Skybox image missing or unreadable: %s", path); 906 toastf(TOAST_ERROR, "Skybox image missing or unreadable: %s", path);
@@ -908,12 +915,9 @@ void load_sky_texture() {
908 // is called on Download() and again on device restore. 915 // is called on Download() and again on device restore.
909 using CreateProcFn = void *(*)(void *, const char *, const char *, int, int, 916 using CreateProcFn = void *(*)(void *, const char *, const char *, int, int,
910 int, int); 917 int, int);
911 // 0x40 = TEXTUREFLAGS_SRGB: the LDR sky path decodes sRGB from the texture 918 g_sky_tex = vfunc<CreateProcFn>(ms, 81)(
912 // flags, the HDR path forces sRGB reads off and does its own conversion. 919 ms, "hl2hake/skybox", GROUP_SKYBOX, w, h, 0 /* RGBA8888 */,
913 int tex_flags = 0x10C /* CLAMPS|CLAMPT|NOMIP */ | (hdr ? 0 : 0x40); 920 0x10C /* CLAMPS|CLAMPT|NOMIP */);
914 g_sky_tex = vfunc<CreateProcFn>(ms, 81)(ms, "hl2hake/skybox",
915 GROUP_SKYBOX, w, h, 0 /*RGBA8888*/,
916 tex_flags);
917 if (!g_sky_tex) { 921 if (!g_sky_tex) {
918 logf("skybox: CreateProceduralTexture failed for %dx%d", w, h); 922 logf("skybox: CreateProceduralTexture failed for %dx%d", w, h);
919 return; 923 return;
@@ -922,6 +926,7 @@ void load_sky_texture() {
922 using DownloadFn = void (*)(void *, void *, int); 926 using DownloadFn = void (*)(void *, void *, int);
923 vfunc<SetRegenFn>(g_sky_tex, 12)(g_sky_tex, &g_sky_regenerator); 927 vfunc<SetRegenFn>(g_sky_tex, 12)(g_sky_tex, &g_sky_regenerator);
924 vfunc<DownloadFn>(g_sky_tex, 13)(g_sky_tex, nullptr, 0); 928 vfunc<DownloadFn>(g_sky_tex, 13)(g_sky_tex, nullptr, 0);
929 g_sky_compensated = false;
925 g_sky_fail_logged = false; 930 g_sky_fail_logged = false;
926 logf("skybox: image %s (%dx%d) uploaded", path, w, h); 931 logf("skybox: image %s (%dx%d) uploaded", path, w, h);
927} 932}