diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 00:07:36 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 00:07:36 +0100 |
| commit | 49d6e90919bbc4f287013e429048afaf340d13b9 (patch) | |
| tree | d46fb3c1dd8d78a75bd3df3f066ef90c0821b355 /src/world.cpp | |
| parent | World: replace skybox changer with a PNG skybox image (diff) | |
| download | hl2h-49d6e90919bbc4f287013e429048afaf340d13b9.tar.gz hl2h-49d6e90919bbc4f287013e429048afaf340d13b9.zip | |
Skybox image: upload via procedural texture, not CreateTextureFromBits
The crash was CreateTextureFromBits: this build's IMaterialSystem interface
stops at GetDisplayDeviceName, and the slots after it are CMaterialSystem's
own virtuals, so index 134 was an unrelated function.
Use CreateProceduralTexture (81, verified by disassembling the interface
vtable) together with an ITextureRegenerator (SetTextureRegenerator 12,
Download 13) that fills mip 0 via IVTFTexture::ImageData (39; IVTFTexture has
a two-slot virtual destructor, so Width=15, Height=16, RowSizeInBytes=19).
Diffstat (limited to 'src/world.cpp')
| -rw-r--r-- | src/world.cpp | 66 |
1 files changed, 60 insertions, 6 deletions
diff --git a/src/world.cpp b/src/world.cpp index cf85795..391ab1a 100644 --- a/src/world.cpp +++ b/src/world.cpp | |||
| @@ -409,6 +409,49 @@ bool load_png_rgba(const char *path, std::vector<unsigned char> &pixels, | |||
| 409 | return true; | 409 | return true; |
| 410 | } | 410 | } |
| 411 | 411 | ||
| 412 | std::vector<unsigned char> g_sky_pixels; | ||
| 413 | int g_sky_px_w = 0, g_sky_px_h = 0; | ||
| 414 | |||
| 415 | // ITextureRegenerator has two virtuals: RegenerateTextureBits then Release. | ||
| 416 | // This build has no IMaterialSystem::CreateTextureFromBits (its interface stops | ||
| 417 | // at GetDisplayDeviceName), so the pixels are uploaded through a procedural | ||
| 418 | // texture + regenerator instead. | ||
| 419 | class SkyRegenerator { | ||
| 420 | public: | ||
| 421 | virtual void RegenerateTextureBits(void *texture, void *vtf, void *rect) { | ||
| 422 | (void)texture; | ||
| 423 | (void)rect; | ||
| 424 | if (!vtf || g_sky_pixels.empty()) | ||
| 425 | return; | ||
| 426 | using IntFn = int (*)(void *); | ||
| 427 | using PitchFn = int (*)(void *, int); | ||
| 428 | using DataFn = unsigned char *(*)(void *, int, int, int); | ||
| 429 | // IVTFTexture vtable: dtor takes 2 slots, then Init, so Width=15, | ||
| 430 | // Height=16, RowSizeInBytes=19, ImageData(frame,face,mip)=39. | ||
| 431 | int w = vfunc<IntFn>(vtf, 15)(vtf); // IVTFTexture::Width | ||
| 432 | int h = vfunc<IntFn>(vtf, 16)(vtf); // IVTFTexture::Height | ||
| 433 | if (w <= 0 || h <= 0) | ||
| 434 | return; | ||
| 435 | int pitch = vfunc<PitchFn>(vtf, 19)(vtf, 0); // RowSizeInBytes(0) | ||
| 436 | unsigned char *dst = vfunc<DataFn>(vtf, 39)(vtf, 0, 0, 0); // ImageData | ||
| 437 | if (!dst || pitch <= 0) | ||
| 438 | return; | ||
| 439 | int rows = h < g_sky_px_h ? h : g_sky_px_h; | ||
| 440 | int src_stride = g_sky_px_w * 4; | ||
| 441 | int copy = w * 4; | ||
| 442 | if (copy > src_stride) | ||
| 443 | copy = src_stride; | ||
| 444 | for (int y = 0; y < rows; y++) { | ||
| 445 | const unsigned char *src = | ||
| 446 | g_sky_pixels.data() + (size_t)y * (size_t)src_stride; | ||
| 447 | memcpy(dst + (size_t)y * (size_t)pitch, src, (size_t)copy); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | virtual void Release() {} | ||
| 451 | }; | ||
| 452 | |||
| 453 | SkyRegenerator g_sky_regenerator; | ||
| 454 | |||
| 412 | void *g_sky_tex = nullptr; | 455 | void *g_sky_tex = nullptr; |
| 413 | bool g_sky_tex_tried = false; | 456 | bool g_sky_tex_tried = false; |
| 414 | bool g_sky_applied = false; | 457 | bool g_sky_applied = false; |
| @@ -573,16 +616,27 @@ void load_sky_texture() { | |||
| 573 | } | 616 | } |
| 574 | return; | 617 | return; |
| 575 | } | 618 | } |
| 576 | using CreateTexFn = void *(*)(void *, int, int, int, int, int, | 619 | g_sky_pixels.swap(pixels); |
| 577 | unsigned char *); | 620 | g_sky_px_w = w; |
| 578 | g_sky_tex = vfunc<CreateTexFn>(ms, 134)(ms, w, h, 1, 0 /* RGBA8888 */, | 621 | g_sky_px_h = h; |
| 579 | (int)pixels.size(), pixels.data()); | 622 | |
| 623 | // The procedural texture keeps its own copy through the regenerator, which | ||
| 624 | // is called on Download() and again on device restore. | ||
| 625 | using CreateProcFn = void *(*)(void *, const char *, const char *, int, int, | ||
| 626 | int, int); | ||
| 627 | g_sky_tex = vfunc<CreateProcFn>(ms, 81)( | ||
| 628 | ms, "hl2hake/skybox", GROUP_SKYBOX, w, h, 0 /* RGBA8888 */, | ||
| 629 | 0x10C /* CLAMPS|CLAMPT|NOMIP */); | ||
| 580 | if (!g_sky_tex) { | 630 | if (!g_sky_tex) { |
| 581 | logf("skybox: CreateTextureFromBits failed for %dx%d", w, h); | 631 | logf("skybox: CreateProceduralTexture failed for %dx%d", w, h); |
| 582 | return; | 632 | return; |
| 583 | } | 633 | } |
| 634 | using SetRegenFn = void (*)(void *, void *); | ||
| 635 | using DownloadFn = void (*)(void *, void *, int); | ||
| 636 | vfunc<SetRegenFn>(g_sky_tex, 12)(g_sky_tex, &g_sky_regenerator); | ||
| 637 | vfunc<DownloadFn>(g_sky_tex, 13)(g_sky_tex, nullptr, 0); | ||
| 584 | g_sky_fail_logged = false; | 638 | g_sky_fail_logged = false; |
| 585 | logf("skybox: image %s (%dx%d) loaded", path, w, h); | 639 | logf("skybox: image %s (%dx%d) uploaded", path, w, h); |
| 586 | } | 640 | } |
| 587 | 641 | ||
| 588 | } // namespace | 642 | } // namespace |
