diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 00:59:41 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 00:59:41 +0100 |
| commit | 1e5054ceac42c97a63428a4adb89395b98890fe1 (patch) | |
| tree | 65ab35f820d4e9c3abccffd47c78b8c279cdb496 /src | |
| parent | Skybox image: recognise Sky_HDR_DX9 as the HDR sky shader (diff) | |
| download | hl2h-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.cpp | 59 |
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 | ||
| 389 | bool load_png_rgba(const char *path, std::vector<unsigned char> &pixels, | 389 | bool 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; | |||
| 692 | void *g_sky_tex = nullptr; | 674 | void *g_sky_tex = nullptr; |
| 693 | bool g_sky_tex_tried = false; | 675 | bool g_sky_tex_tried = false; |
| 694 | bool g_sky_applied = false; | 676 | bool g_sky_applied = false; |
| 677 | bool g_sky_compensated = false; | ||
| 695 | bool g_sky_reload = false; | 678 | bool g_sky_reload = false; |
| 696 | double g_sky_retry_at = 0.0; | 679 | double g_sky_retry_at = 0.0; |
| 697 | bool g_sky_fail_logged = false; | 680 | bool 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 | ||
| 715 | bool sky_uses_hdr_shader(); | ||
| 716 | |||
| 732 | int apply_sky_texture() { | 717 | int 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 | } |
