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). --- src/world.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 6 deletions(-) (limited to 'src/world.cpp') 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