diff options
| -rw-r--r-- | CONTEXT.md | 8 | ||||
| -rw-r--r-- | src/hl2h.cpp | 4 | ||||
| -rw-r--r-- | src/menu.cpp | 1 | ||||
| -rw-r--r-- | src/world.cpp | 58 |
4 files changed, 69 insertions, 2 deletions
| @@ -368,7 +368,9 @@ vocabulary / decisions. | |||
| 368 | the INI). Not `sv_skyname`: that did not take effect at runtime in this | 368 | the INI). Not `sv_skyname`: that did not take effect at runtime in this |
| 369 | build, so the skybox card now works on materials instead. | 369 | build, so the skybox card now works on materials instead. |
| 370 | - The PNG is decoded by our own reader (`zlib`'s `uncompress` + PNG filters; | 370 | - The PNG is decoded by our own reader (`zlib`'s `uncompress` + PNG filters; |
| 371 | 8-bit non-interlaced RGB/RGBA/gray/palette). Do **not** use the system | 371 | 8-bit non-interlaced RGB/RGBA/gray/palette) and scaled to the next power of |
| 372 | two, because the material system rejects textures whose dimensions are not | ||
| 373 | multiples of 4 ("Image dimensions must be multiple of 4!"). Do **not** use the system | ||
| 372 | libpng: `client.so`, `engine.so` and `vguimatsurface.so` each export their | 374 | libpng: `client.so`, `engine.so` and `vguimatsurface.so` each export their |
| 373 | own `png_create_read_struct`, so libpng16's internal PLT calls bind to the | 375 | own `png_create_read_struct`, so libpng16's internal PLT calls bind to the |
| 374 | game's older libpng with a different `png_struct` layout and crash in | 376 | game's older libpng with a different `png_struct` layout and crash in |
| @@ -394,6 +396,10 @@ vocabulary / decisions. | |||
| 394 | string as a fallback) and restored when the feature is disabled. Records | 396 | string as a fallback) and restored when the feature is disabled. Records |
| 395 | survive level changes so a reused sky material can still be restored; the | 397 | survive level changes so a reused sky material can still be restored; the |
| 396 | override is re-applied to the new map automatically. | 398 | override is re-applied to the new map automatically. |
| 399 | - `skybox_update()` runs from the `FRAME_RENDER_START` FrameStageNotify hook, | ||
| 400 | not from the VGUI paint hook: `ITexture::Download` only regenerates texture | ||
| 401 | bits on a render-safe frame stage. Running it during paint skipped the | ||
| 402 | regenerator and left an empty VTF (upload warnings + crash). | ||
| 397 | - The card shows the image path and has a Reload image button. A missing or | 403 | - The card shows the image path and has a Reload image button. A missing or |
| 398 | unreadable PNG raises an error toast; the image is only re-read on Reload. | 404 | unreadable PNG raises an error toast; the image is only re-read on Reload. |
| 399 | - Saved as `[skybox] active/bind`. | 405 | - Saved as `[skybox] active/bind`. |
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) { | |||
| 770 | if (oFrameStageNotify) | 770 | if (oFrameStageNotify) |
| 771 | oFrameStageNotify(self, stage); | 771 | oFrameStageNotify(self, stage); |
| 772 | if (stage == 5) { | 772 | if (stage == 5) { |
| 773 | // Skybox texture uploads happen here, not in the VGUI paint hook: the | ||
| 774 | // material system only downloads procedural textures on a render-safe | ||
| 775 | // frame stage. | ||
| 776 | skybox_update(); | ||
| 773 | if (g_silent_restore_pending) { | 777 | if (g_silent_restore_pending) { |
| 774 | g_silent_restore_pending = false; | 778 | g_silent_restore_pending = false; |
| 775 | if (g_iface.engine) { | 779 | 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() { | |||
| 816 | fullbright_update(); | 816 | fullbright_update(); |
| 817 | world_tint_update(); | 817 | world_tint_update(); |
| 818 | fog_update(); | 818 | fog_update(); |
| 819 | skybox_update(); | ||
| 820 | gui_handle_input(); | 819 | gui_handle_input(); |
| 821 | // HL2H_DRAW_TEST=1 draws a probe rect without needing the GUI open | 820 | // HL2H_DRAW_TEST=1 draws a probe rect without needing the GUI open |
| 822 | static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; | 821 | 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<unsigned char> &pixels, | |||
| 557 | } | 557 | } |
| 558 | } | 558 | } |
| 559 | 559 | ||
| 560 | // The material system rejects textures whose dimensions are not multiples of | ||
| 561 | // 4 ("Image dimensions must be multiple of 4!" / "LoadTextureFromVTF: VTF | ||
| 562 | // dimensions do not match texture"). Scale to the next power of two; that is | ||
| 563 | // also what the old skybox code expects. | ||
| 564 | { | ||
| 565 | int tw = 1, th = 1; | ||
| 566 | while (tw < (int)w) | ||
| 567 | tw <<= 1; | ||
| 568 | while (th < (int)h) | ||
| 569 | th <<= 1; | ||
| 570 | if (tw > 4096) tw = 4096; | ||
| 571 | if (th > 4096) th = 4096; | ||
| 572 | if (tw != (int)w || th != (int)h) { | ||
| 573 | std::vector<unsigned char> scaled((size_t)tw * (size_t)th * 4); | ||
| 574 | for (int y = 0; y < th; y++) { | ||
| 575 | float sy = ((float)y + 0.5f) * (float)h / (float)th - 0.5f; | ||
| 576 | int y0 = (int)sy; | ||
| 577 | float fy = sy - (float)y0; | ||
| 578 | if (y0 < 0) { y0 = 0; fy = 0.0f; } | ||
| 579 | int y1 = y0 + 1 < (int)h ? y0 + 1 : y0; | ||
| 580 | for (int x = 0; x < tw; x++) { | ||
| 581 | float sx = ((float)x + 0.5f) * (float)w / (float)tw - 0.5f; | ||
| 582 | int x0 = (int)sx; | ||
| 583 | float fx = sx - (float)x0; | ||
| 584 | if (x0 < 0) { x0 = 0; fx = 0.0f; } | ||
| 585 | int x1 = x0 + 1 < (int)w ? x0 + 1 : x0; | ||
| 586 | const unsigned char *p00 = pixels.data() + | ||
| 587 | ((size_t)y0 * (size_t)w + (size_t)x0) * 4; | ||
| 588 | const unsigned char *p10 = pixels.data() + | ||
| 589 | ((size_t)y0 * (size_t)w + (size_t)x1) * 4; | ||
| 590 | const unsigned char *p01 = pixels.data() + | ||
| 591 | ((size_t)y1 * (size_t)w + (size_t)x0) * 4; | ||
| 592 | const unsigned char *p11 = pixels.data() + | ||
| 593 | ((size_t)y1 * (size_t)w + (size_t)x1) * 4; | ||
| 594 | unsigned char *o = scaled.data() + | ||
| 595 | ((size_t)y * (size_t)tw + (size_t)x) * 4; | ||
| 596 | for (int c = 0; c < 4; c++) { | ||
| 597 | float v = (1.0f - fy) * | ||
| 598 | ((1.0f - fx) * p00[c] + fx * p10[c]) + | ||
| 599 | fy * ((1.0f - fx) * p01[c] + fx * p11[c]); | ||
| 600 | o[c] = (unsigned char)(v + 0.5f); | ||
| 601 | } | ||
| 602 | } | ||
| 603 | } | ||
| 604 | logf("skybox: resized %dx%d -> %dx%d for the material system", | ||
| 605 | (int)w, (int)h, tw, th); | ||
| 606 | pixels.swap(scaled); | ||
| 607 | w = (uint32_t)tw; | ||
| 608 | h = (uint32_t)th; | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 560 | out_w = (int)w; | 612 | out_w = (int)w; |
| 561 | out_h = (int)h; | 613 | out_h = (int)h; |
| 562 | return true; | 614 | return true; |
| @@ -574,6 +626,12 @@ public: | |||
| 574 | virtual void RegenerateTextureBits(void *texture, void *vtf, void *rect) { | 626 | virtual void RegenerateTextureBits(void *texture, void *vtf, void *rect) { |
| 575 | (void)texture; | 627 | (void)texture; |
| 576 | (void)rect; | 628 | (void)rect; |
| 629 | static bool entered = false; | ||
| 630 | if (!entered) { | ||
| 631 | entered = true; | ||
| 632 | logf("skybox: regenerator entered (vtf=%p, %zu pixels)", vtf, | ||
| 633 | g_sky_pixels.size()); | ||
| 634 | } | ||
| 577 | if (!vtf || g_sky_pixels.empty()) | 635 | if (!vtf || g_sky_pixels.empty()) |
| 578 | return; | 636 | return; |
| 579 | using IntFn = int (*)(void *); | 637 | using IntFn = int (*)(void *); |
