From 4632788c4bef265e58630473eed5559bf125a726 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 00:29:24 +0100 Subject: Skybox image: power-of-two resize; upload at FRAME_RENDER_START - The material system rejects textures whose dimensions are not multiples of 4 ("Image dimensions must be multiple of 4!"), so scale the decoded image to the next power of two (bilinear). 798x798 -> 1024x1024. The invalid VTF also crashed the renderer after the apply. - Move skybox_update() out of the VGUI paint hook into the FRAME_RENDER_START FrameStageNotify stage. ITexture::Download only regenerates bits when the material system considers the frame render-safe; during paint it returned early, the regenerator never ran (no pixels), and a later upload of the empty VTF crashed. - Log the regenerator entry so the log shows whether the upload path ran. --- src/hl2h.cpp | 4 ++++ src/menu.cpp | 1 - src/world.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 4ba1af3..28b2995 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -770,6 +770,10 @@ static void hkdFrameStageNotify(void *self, int stage) { if (oFrameStageNotify) oFrameStageNotify(self, stage); if (stage == 5) { + // Skybox texture uploads happen here, not in the VGUI paint hook: the + // material system only downloads procedural textures on a render-safe + // frame stage. + skybox_update(); if (g_silent_restore_pending) { g_silent_restore_pending = false; if (g_iface.engine) { diff --git a/src/menu.cpp b/src/menu.cpp index 92131d4..7ffc84a 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -816,7 +816,6 @@ void outer_draw() { fullbright_update(); world_tint_update(); fog_update(); - skybox_update(); gui_handle_input(); // HL2H_DRAW_TEST=1 draws a probe rect without needing the GUI open static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; diff --git a/src/world.cpp b/src/world.cpp index aaee058..1228c7a 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -557,6 +557,58 @@ bool load_png_rgba(const char *path, std::vector &pixels, } } + // The material system rejects textures whose dimensions are not multiples of + // 4 ("Image dimensions must be multiple of 4!" / "LoadTextureFromVTF: VTF + // dimensions do not match texture"). Scale to the next power of two; that is + // also what the old skybox code expects. + { + int tw = 1, th = 1; + while (tw < (int)w) + tw <<= 1; + while (th < (int)h) + th <<= 1; + if (tw > 4096) tw = 4096; + if (th > 4096) th = 4096; + if (tw != (int)w || th != (int)h) { + std::vector scaled((size_t)tw * (size_t)th * 4); + for (int y = 0; y < th; y++) { + float sy = ((float)y + 0.5f) * (float)h / (float)th - 0.5f; + int y0 = (int)sy; + float fy = sy - (float)y0; + if (y0 < 0) { y0 = 0; fy = 0.0f; } + int y1 = y0 + 1 < (int)h ? y0 + 1 : y0; + for (int x = 0; x < tw; x++) { + float sx = ((float)x + 0.5f) * (float)w / (float)tw - 0.5f; + int x0 = (int)sx; + float fx = sx - (float)x0; + if (x0 < 0) { x0 = 0; fx = 0.0f; } + int x1 = x0 + 1 < (int)w ? x0 + 1 : x0; + const unsigned char *p00 = pixels.data() + + ((size_t)y0 * (size_t)w + (size_t)x0) * 4; + const unsigned char *p10 = pixels.data() + + ((size_t)y0 * (size_t)w + (size_t)x1) * 4; + const unsigned char *p01 = pixels.data() + + ((size_t)y1 * (size_t)w + (size_t)x0) * 4; + const unsigned char *p11 = pixels.data() + + ((size_t)y1 * (size_t)w + (size_t)x1) * 4; + unsigned char *o = scaled.data() + + ((size_t)y * (size_t)tw + (size_t)x) * 4; + for (int c = 0; c < 4; c++) { + float v = (1.0f - fy) * + ((1.0f - fx) * p00[c] + fx * p10[c]) + + fy * ((1.0f - fx) * p01[c] + fx * p11[c]); + o[c] = (unsigned char)(v + 0.5f); + } + } + } + logf("skybox: resized %dx%d -> %dx%d for the material system", + (int)w, (int)h, tw, th); + pixels.swap(scaled); + w = (uint32_t)tw; + h = (uint32_t)th; + } + } + out_w = (int)w; out_h = (int)h; return true; @@ -574,6 +626,12 @@ public: virtual void RegenerateTextureBits(void *texture, void *vtf, void *rect) { (void)texture; (void)rect; + static bool entered = false; + if (!entered) { + entered = true; + logf("skybox: regenerator entered (vtf=%p, %zu pixels)", vtf, + g_sky_pixels.size()); + } if (!vtf || g_sky_pixels.empty()) return; using IntFn = int (*)(void *); -- cgit v1.2.3