diff options
| -rw-r--r-- | CONTEXT.md | 9 | ||||
| -rw-r--r-- | src/world.cpp | 66 |
2 files changed, 67 insertions, 8 deletions
| @@ -364,8 +364,13 @@ vocabulary / decisions. | |||
| 364 | the INI). Not `sv_skyname`: that did not take effect at runtime in this | 364 | the INI). Not `sv_skyname`: that did not take effect at runtime in this |
| 365 | build, so the skybox card now works on materials instead. | 365 | build, so the skybox card now works on materials instead. |
| 366 | - PNG is decoded with libpng16's simplified API (`dlopen`ed, no link | 366 | - PNG is decoded with libpng16's simplified API (`dlopen`ed, no link |
| 367 | dependency); the RGBA pixels become an `ITexture` via | 367 | dependency). This build's `IMaterialSystem` interface ends at |
| 368 | `IMaterialSystem::CreateTextureFromBits` (RGBA8888, one mip). | 368 | `GetDisplayDeviceName` (the slots after it belong to `CMaterialSystem` |
| 369 | itself), so there is **no `CreateTextureFromBits`**. The pixels are uploaded | ||
| 370 | through `CreateProceduralTexture` (vtable 81) + an `ITextureRegenerator` | ||
| 371 | (`SetTextureRegenerator` 12, `Download` 13), which fills mip 0 through | ||
| 372 | `IVTFTexture::ImageData(frame,face,mip)` (39). `IVTFTexture`'s virtual | ||
| 373 | destructor occupies two slots, so Width=15, Height=16, RowSizeInBytes=19. | ||
| 369 | - The texture is assigned with `IMaterialVar::SetTextureValue` to | 374 | - The texture is assigned with `IMaterialVar::SetTextureValue` to |
| 370 | `$basetexture`, plus `$hdrbasetexture` / `$hdrcompressedtexture` when those | 375 | `$basetexture`, plus `$hdrbasetexture` / `$hdrcompressedtexture` when those |
| 371 | vars exist, on every material in the `SkyBox textures` group or named | 376 | 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<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 |
