diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 00:21:55 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 00:21:55 +0100 |
| commit | b97ac5d495ce82e6f1cf051c3f3a29ff38c91982 (patch) | |
| tree | 7b16b311fb9fea94d4d160cc4cdd634f13033c25 | |
| parent | Skybox image: decode PNG ourselves to avoid symbol interposition (diff) | |
| download | hl2h-b97ac5d495ce82e6f1cf051c3f3a29ff38c91982.tar.gz hl2h-b97ac5d495ce82e6f1cf051c3f3a29ff38c91982.zip | |
Skybox image: fix HDR sky binding; clip checkbox/arrow drawing
The HDR sky materials use the "sky" shader with $hdrcompressedtexture (DXT5).
Assigning an RGBA8 procedural texture there sampled as compressed data and
rendered black. Point $hdrbasetexture at the image, undefine
$hdrcompressedtexture (and its 0/1/2 variants) so the shader falls back, and
call IMaterial::Refresh (37) because the sky shader binds textures in
SHADER_INIT, which is otherwise cached.
GUI: draw_checkbox and the card expand chevron ignored the content clip, so
cards scrolled out of view leaked header widgets outside the window.
Log the first regenerator call (size/pitch/first pixel) for diagnosis.
Diffstat (limited to '')
| -rw-r--r-- | CONTEXT.md | 16 | ||||
| -rw-r--r-- | src/gui.cpp | 29 | ||||
| -rw-r--r-- | src/world.cpp | 43 |
3 files changed, 65 insertions, 23 deletions
| @@ -144,6 +144,10 @@ vocabulary / decisions. | |||
| 144 | `x`, `y`, `category` and an `expanded` bitmask (card index order). Every | 144 | `x`, `y`, `category` and an `expanded` bitmask (card index order). Every |
| 145 | feature now persists an `active` and `bind` pair, including movement, melee | 145 | feature now persists an `active` and `bind` pair, including movement, melee |
| 146 | and spread. | 146 | and spread. |
| 147 | - All card contents are clipped to the scrolling content rect, including | ||
| 148 | checkboxes and the expand chevron; only clipped fills/text were handled | ||
| 149 | before, so cards scrolled out of view leaked header widgets outside the | ||
| 150 | window. | ||
| 147 | - **Colour control (`CK_COLOR`)**: row shows a live swatch; clicking the row | 151 | - **Colour control (`CK_COLOR`)**: row shows a live swatch; clicking the row |
| 148 | expands an inline picker (saturation/value square + hue bar + hex/RGB | 152 | expands an inline picker (saturation/value square + hue bar + hex/RGB |
| 149 | preview) instead of three RGB sliders, growing the card in place so it | 153 | preview) instead of three RGB sliders, growing the card in place so it |
| @@ -377,9 +381,15 @@ vocabulary / decisions. | |||
| 377 | `IVTFTexture::ImageData(frame,face,mip)` (39). `IVTFTexture`'s virtual | 381 | `IVTFTexture::ImageData(frame,face,mip)` (39). `IVTFTexture`'s virtual |
| 378 | destructor occupies two slots, so Width=15, Height=16, RowSizeInBytes=19. | 382 | destructor occupies two slots, so Width=15, Height=16, RowSizeInBytes=19. |
| 379 | - The texture is assigned with `IMaterialVar::SetTextureValue` to | 383 | - The texture is assigned with `IMaterialVar::SetTextureValue` to |
| 380 | `$basetexture`, plus `$hdrbasetexture` / `$hdrcompressedtexture` when those | 384 | `$basetexture` and `$hdrbasetexture` on every material in the `SkyBox |
| 381 | vars exist, on every material in the `SkyBox textures` group or named | 385 | textures` group or named `skybox/*`. The same image maps onto all six faces. |
| 382 | `skybox/*`. The same image is mapped onto all six faces. | 386 | - HDR sky materials use the `sky` shader, whose texture binding happens in |
| 387 | `SHADER_INIT`: it samples `$hdrcompressedtexture` (DXT5) when defined and | ||
| 388 | otherwise `$hdrbasetexture`. Putting an RGBA8 texture into the DXT5 slot | ||
| 389 | renders black, so `$hdrcompressedtexture` (+ its `0/1/2` variants) is | ||
| 390 | `SetUndefined()` and `$hdrbasetexture` is pointed at our texture instead. | ||
| 391 | The shader state is then rebuilt with `IMaterial::Refresh()` (slot 37), | ||
| 392 | otherwise the old bindings stay cached. | ||
| 383 | - Original textures are recorded per material/var (texture pointer plus the | 393 | - Original textures are recorded per material/var (texture pointer plus the |
| 384 | string as a fallback) and restored when the feature is disabled. Records | 394 | string as a fallback) and restored when the feature is disabled. Records |
| 385 | survive level changes so a reused sky material can still be restored; the | 395 | survive level changes so a reused sky material can still be restored; the |
diff --git a/src/gui.cpp b/src/gui.cpp index eb53dff..ebc1f62 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -1672,10 +1672,11 @@ void gui_handle_input() { | |||
| 1672 | // Draw | 1672 | // Draw |
| 1673 | // --------------------------------------------------------------------------- | 1673 | // --------------------------------------------------------------------------- |
| 1674 | 1674 | ||
| 1675 | static void draw_checkbox(int x, int y, bool on) { | 1675 | static void draw_checkbox(int x, int y, bool on, |
| 1676 | const Rect *clip = nullptr) { | ||
| 1676 | Rect box{x, y, S(15.0f), S(15.0f)}; | 1677 | Rect box{x, y, S(15.0f), S(15.0f)}; |
| 1677 | fill(box, on ? C_ACCENT : Col{30, 34, 42, 255}); | 1678 | fill(box, on ? C_ACCENT : Col{30, 34, 42, 255}, clip); |
| 1678 | outline(box, C_BORDER); | 1679 | outline(box, C_BORDER, clip); |
| 1679 | if (on) { | 1680 | if (on) { |
| 1680 | line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG); | 1681 | line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG); |
| 1681 | line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG); | 1682 | line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG); |
| @@ -1719,14 +1720,17 @@ static void draw_card(const Rect &clip, int index, int y) { | |||
| 1719 | Rect head{cr.x, cr.y, cr.w, hh}; | 1720 | Rect head{cr.x, cr.y, cr.w, hh}; |
| 1720 | fill(head, C_HEADER, &clip); | 1721 | fill(head, C_HEADER, &clip); |
| 1721 | 1722 | ||
| 1722 | // expand arrow | 1723 | // expand arrow (clipped like the rest of the card; header rows of cards |
| 1724 | // scrolled out of view must not leak into the window chrome) | ||
| 1723 | int ax = cr.x + S(10.0f), ay = cr.y + (hh - S(8.0f)) / 2; | 1725 | int ax = cr.x + S(10.0f), ay = cr.y + (hh - S(8.0f)) / 2; |
| 1724 | if (c.expanded) { | 1726 | if (ay >= clip.y && ay + S(8.0f) <= clip.y + clip.h) { |
| 1725 | line(ax, ay, ax + S(4.0f), ay + S(5.0f), C_DIM); | 1727 | if (c.expanded) { |
| 1726 | line(ax + S(4.0f), ay + S(5.0f), ax + S(8.0f), ay, C_DIM); | 1728 | line(ax, ay, ax + S(4.0f), ay + S(5.0f), C_DIM); |
| 1727 | } else { | 1729 | line(ax + S(4.0f), ay + S(5.0f), ax + S(8.0f), ay, C_DIM); |
| 1728 | line(ax, ay, ax + S(5.0f), ay + S(4.0f), C_DIM); | 1730 | } else { |
| 1729 | line(ax + S(5.0f), ay + S(4.0f), ax, ay + S(8.0f), C_DIM); | 1731 | line(ax, ay, ax + S(5.0f), ay + S(4.0f), C_DIM); |
| 1732 | line(ax + S(5.0f), ay + S(4.0f), ax, ay + S(8.0f), C_DIM); | ||
| 1733 | } | ||
| 1730 | } | 1734 | } |
| 1731 | 1735 | ||
| 1732 | // title (wraps instead of truncating) | 1736 | // title (wraps instead of truncating) |
| @@ -1741,7 +1745,7 @@ static void draw_card(const Rect &clip, int index, int y) { | |||
| 1741 | if (c.feat) { | 1745 | if (c.feat) { |
| 1742 | bool active = c.feat->active; | 1746 | bool active = c.feat->active; |
| 1743 | int active_y = cr.y + (hh - S(18.0f)) / 2; | 1747 | int active_y = cr.y + (hh - S(18.0f)) / 2; |
| 1744 | draw_checkbox(cr.x + cr.w - S(170.0f), active_y, active); | 1748 | draw_checkbox(cr.x + cr.w - S(170.0f), active_y, active, &clip); |
| 1745 | text_in(clip, cr.x + cr.w - S(148.0f), | 1749 | text_in(clip, cr.x + cr.w - S(148.0f), |
| 1746 | active_y + (S(18.0f) - g_body_h) / 2, active ? "On" : "Off", | 1750 | active_y + (S(18.0f) - g_body_h) / 2, active ? "On" : "Off", |
| 1747 | g_font_body, g_body_h, 0, active ? C_GOOD : C_DIM); | 1751 | g_font_body, g_body_h, 0, active ? C_GOOD : C_DIM); |
| @@ -1798,7 +1802,8 @@ static void draw_card(const Rect &clip, int index, int y) { | |||
| 1798 | maxw, C_TEXT, S(2.0f)); | 1802 | maxw, C_TEXT, S(2.0f)); |
| 1799 | if (ct.kind == CK_TOGGLE) { | 1803 | if (ct.kind == CK_TOGGLE) { |
| 1800 | bool on = ctrl_val(ct) != 0; | 1804 | bool on = ctrl_val(ct) != 0; |
| 1801 | draw_checkbox(cr.x + cr.w - S(31.0f), by + (rh - S(15.0f)) / 2, on); | 1805 | draw_checkbox(cr.x + cr.w - S(31.0f), by + (rh - S(15.0f)) / 2, on, |
| 1806 | &clip); | ||
| 1802 | text_in(clip, cr.x + cr.w - S(58.0f), by + (rh - g_body_h) / 2, | 1807 | text_in(clip, cr.x + cr.w - S(58.0f), by + (rh - g_body_h) / 2, |
| 1803 | on ? "On" : "Off", g_font_body, g_body_h, 0, | 1808 | on ? "On" : "Off", g_font_body, g_body_h, 0, |
| 1804 | on ? C_GOOD : C_DIM); | 1809 | on ? C_GOOD : C_DIM); |
diff --git a/src/world.cpp b/src/world.cpp index 621e5f7..aaee058 100644 --- a/src/world.cpp +++ b/src/world.cpp | |||
| @@ -587,6 +587,13 @@ public: | |||
| 587 | return; | 587 | return; |
| 588 | int pitch = vfunc<PitchFn>(vtf, 19)(vtf, 0); // RowSizeInBytes(0) | 588 | int pitch = vfunc<PitchFn>(vtf, 19)(vtf, 0); // RowSizeInBytes(0) |
| 589 | unsigned char *dst = vfunc<DataFn>(vtf, 39)(vtf, 0, 0, 0); // ImageData | 589 | unsigned char *dst = vfunc<DataFn>(vtf, 39)(vtf, 0, 0, 0); // ImageData |
| 590 | static bool logged_regen = false; | ||
| 591 | if (!logged_regen) { | ||
| 592 | logged_regen = true; | ||
| 593 | logf("skybox: regenerator %dx%d pitch=%d dst=%p first=%02x%02x%02x%02x", | ||
| 594 | w, h, pitch, dst, g_sky_pixels[0], g_sky_pixels[1], | ||
| 595 | g_sky_pixels[2], g_sky_pixels[3]); | ||
| 596 | } | ||
| 590 | if (!dst || pitch <= 0) | 597 | if (!dst || pitch <= 0) |
| 591 | return; | 598 | return; |
| 592 | int rows = h < g_sky_px_h ? h : g_sky_px_h; | 599 | int rows = h < g_sky_px_h ? h : g_sky_px_h; |
| @@ -675,24 +682,44 @@ int apply_sky_texture() { | |||
| 675 | using SetTexFn = void (*)(void *, void *); | 682 | using SetTexFn = void (*)(void *, void *); |
| 676 | auto find_var = vfunc<FindVarFn>(mat, 11); // IMaterial::FindVar | 683 | auto find_var = vfunc<FindVarFn>(mat, 11); // IMaterial::FindVar |
| 677 | 684 | ||
| 678 | static const char *vars[] = {"$basetexture", "$hdrbasetexture", | 685 | using GetTexFn = void *(*)(void *); |
| 679 | "$hdrcompressedtexture", nullptr}; | 686 | using IsDefFn = bool (*)(void *); |
| 680 | for (int vi = 0; vars[vi]; vi++) { | 687 | using UndefFn = void (*)(void *); |
| 688 | static const char *tex_vars[] = {"$basetexture", "$hdrbasetexture", | ||
| 689 | nullptr}; | ||
| 690 | for (int vi = 0; tex_vars[vi]; vi++) { | ||
| 681 | bool found = false; | 691 | bool found = false; |
| 682 | void *var = find_var(mat, vars[vi], &found, false); | 692 | void *var = find_var(mat, tex_vars[vi], &found, false); |
| 683 | if (!found || !var) | 693 | if (!found || !var) |
| 684 | continue; | 694 | continue; |
| 685 | using GetTexFn = void *(*)(void *); | ||
| 686 | auto vget = vfunc<GetStrFn>(var, 6); // GetStringValue | 695 | auto vget = vfunc<GetStrFn>(var, 6); // GetStringValue |
| 687 | auto vgettex = vfunc<GetTexFn>(var, 0); // GetTextureValue | 696 | auto vgettex = vfunc<GetTexFn>(var, 0); // GetTextureValue |
| 688 | auto vset = vfunc<SetTexFn>(var, 14); // SetTextureValue | 697 | auto vset = vfunc<SetTexFn>(var, 14); // SetTextureValue |
| 689 | const char *orig = vget(var); | 698 | record_orig(name, tex_vars[vi], vget(var), vgettex(var)); |
| 690 | void *orig_tex = vgettex(var); | ||
| 691 | record_orig(name, vars[vi], orig, orig_tex); | ||
| 692 | vset(var, g_sky_tex); | 699 | vset(var, g_sky_tex); |
| 693 | if (vi == 0) | 700 | if (vi == 0) |
| 694 | hits++; | 701 | hits++; |
| 695 | } | 702 | } |
| 703 | // The HDR sky shader samples $hdrcompressedtexture (DXT5) when defined, | ||
| 704 | // which cannot hold our RGBA texture. Undefine it so the shader falls back | ||
| 705 | // to $hdrbasetexture, which now points at our image. | ||
| 706 | static const char *undef_vars[] = { | ||
| 707 | "$hdrcompressedtexture", "$hdrcompressedtexture0", | ||
| 708 | "$hdrcompressedtexture1", "$hdrcompressedtexture2", nullptr}; | ||
| 709 | for (int vi = 0; undef_vars[vi]; vi++) { | ||
| 710 | bool found = false; | ||
| 711 | void *var = find_var(mat, undef_vars[vi], &found, false); | ||
| 712 | if (!found || !var) | ||
| 713 | continue; | ||
| 714 | if (!vfunc<IsDefFn>(var, 17)(var)) | ||
| 715 | continue; | ||
| 716 | record_orig(name, undef_vars[vi], vfunc<GetStrFn>(var, 6)(var), | ||
| 717 | vfunc<GetTexFn>(var, 0)(var)); | ||
| 718 | vfunc<UndefFn>(var, 18)(var); // SetUndefined | ||
| 719 | } | ||
| 720 | // The sky shader binds its texture in SHADER_INIT, so changing the vars is | ||
| 721 | // not enough: rebuild the material's shader state. | ||
| 722 | vfunc<void (*)(void *)>(mat, 37)(mat); // IMaterial::Refresh | ||
| 696 | } | 723 | } |
| 697 | return hits; | 724 | return hits; |
| 698 | } | 725 | } |
