From 49d6e90919bbc4f287013e429048afaf340d13b9 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 00:07:36 +0100 Subject: 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). --- CONTEXT.md | 9 ++++++-- src/world.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index a490403..669c1fb 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -364,8 +364,13 @@ vocabulary / decisions. the INI). Not `sv_skyname`: that did not take effect at runtime in this build, so the skybox card now works on materials instead. - PNG is decoded with libpng16's simplified API (`dlopen`ed, no link - dependency); the RGBA pixels become an `ITexture` via - `IMaterialSystem::CreateTextureFromBits` (RGBA8888, one mip). + dependency). This build's `IMaterialSystem` interface ends at + `GetDisplayDeviceName` (the slots after it belong to `CMaterialSystem` + itself), so there is **no `CreateTextureFromBits`**. The pixels are uploaded + through `CreateProceduralTexture` (vtable 81) + an `ITextureRegenerator` + (`SetTextureRegenerator` 12, `Download` 13), which fills mip 0 through + `IVTFTexture::ImageData(frame,face,mip)` (39). `IVTFTexture`'s virtual + destructor occupies two slots, so Width=15, Height=16, RowSizeInBytes=19. - The texture is assigned with `IMaterialVar::SetTextureValue` to `$basetexture`, plus `$hdrbasetexture` / `$hdrcompressedtexture` when those vars exist, on every material in the `SkyBox textures` group or named 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 &pixels, return true; } +std::vector g_sky_pixels; +int g_sky_px_w = 0, g_sky_px_h = 0; + +// ITextureRegenerator has two virtuals: RegenerateTextureBits then Release. +// This build has no IMaterialSystem::CreateTextureFromBits (its interface stops +// at GetDisplayDeviceName), so the pixels are uploaded through a procedural +// texture + regenerator instead. +class SkyRegenerator { +public: + virtual void RegenerateTextureBits(void *texture, void *vtf, void *rect) { + (void)texture; + (void)rect; + if (!vtf || g_sky_pixels.empty()) + return; + using IntFn = int (*)(void *); + using PitchFn = int (*)(void *, int); + using DataFn = unsigned char *(*)(void *, int, int, int); + // IVTFTexture vtable: dtor takes 2 slots, then Init, so Width=15, + // Height=16, RowSizeInBytes=19, ImageData(frame,face,mip)=39. + int w = vfunc(vtf, 15)(vtf); // IVTFTexture::Width + int h = vfunc(vtf, 16)(vtf); // IVTFTexture::Height + if (w <= 0 || h <= 0) + return; + int pitch = vfunc(vtf, 19)(vtf, 0); // RowSizeInBytes(0) + unsigned char *dst = vfunc(vtf, 39)(vtf, 0, 0, 0); // ImageData + if (!dst || pitch <= 0) + return; + int rows = h < g_sky_px_h ? h : g_sky_px_h; + int src_stride = g_sky_px_w * 4; + int copy = w * 4; + if (copy > src_stride) + copy = src_stride; + for (int y = 0; y < rows; y++) { + const unsigned char *src = + g_sky_pixels.data() + (size_t)y * (size_t)src_stride; + memcpy(dst + (size_t)y * (size_t)pitch, src, (size_t)copy); + } + } + virtual void Release() {} +}; + +SkyRegenerator g_sky_regenerator; + void *g_sky_tex = nullptr; bool g_sky_tex_tried = false; bool g_sky_applied = false; @@ -573,16 +616,27 @@ void load_sky_texture() { } return; } - using CreateTexFn = void *(*)(void *, int, int, int, int, int, - unsigned char *); - g_sky_tex = vfunc(ms, 134)(ms, w, h, 1, 0 /* RGBA8888 */, - (int)pixels.size(), pixels.data()); + g_sky_pixels.swap(pixels); + g_sky_px_w = w; + g_sky_px_h = h; + + // The procedural texture keeps its own copy through the regenerator, which + // is called on Download() and again on device restore. + using CreateProcFn = void *(*)(void *, const char *, const char *, int, int, + int, int); + g_sky_tex = vfunc(ms, 81)( + ms, "hl2hake/skybox", GROUP_SKYBOX, w, h, 0 /* RGBA8888 */, + 0x10C /* CLAMPS|CLAMPT|NOMIP */); if (!g_sky_tex) { - logf("skybox: CreateTextureFromBits failed for %dx%d", w, h); + logf("skybox: CreateProceduralTexture failed for %dx%d", w, h); return; } + using SetRegenFn = void (*)(void *, void *); + using DownloadFn = void (*)(void *, void *, int); + vfunc(g_sky_tex, 12)(g_sky_tex, &g_sky_regenerator); + vfunc(g_sky_tex, 13)(g_sky_tex, nullptr, 0); g_sky_fail_logged = false; - logf("skybox: image %s (%dx%d) loaded", path, w, h); + logf("skybox: image %s (%dx%d) uploaded", path, w, h); } } // namespace -- cgit v1.2.3