From c2917b1dc8c311841aaeb03ca64c10126cb8917d Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 2 Apr 2022 13:05:25 +0100 Subject: [PATCH] DiabloUi: Load animated PCX sprites as CEL Reduces memory usage in menu (and thus the overall allocator pressure) --- Source/DiabloUI/diabloui.cpp | 69 +++++++++++++++++++++++++++--------- Source/DiabloUI/diabloui.h | 5 +-- Source/DiabloUI/title.cpp | 9 +++-- Source/DiabloUI/ui_item.h | 44 +++++++++++++++++++++++ Source/engine/cel_sprite.hpp | 5 +++ 5 files changed, 110 insertions(+), 22 deletions(-) diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 509de86cf..6df21cc6c 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -18,6 +18,9 @@ #include "controls/plrctrls.h" #include "discord/discord.h" #include "dx.h" +#include "engine/cel_sprite.hpp" +#include "engine/load_pcx_as_cel.hpp" +#include "engine/render/cel_render.hpp" #include "hwcursor.hpp" #include "palette.h" #include "utils/display.h" @@ -44,8 +47,10 @@ namespace devilution { -std::array ArtLogos; -std::array ArtFocus; +// These are stored as PCX but we load them as CEL to reduce memory usage. +std::array, 3> ArtLogos; +std::array, 3> ArtFocus; + Art ArtBackgroundWidescreen; Art ArtBackground; Art ArtCursor; @@ -567,13 +572,13 @@ void LoadHeros() void LoadUiGFX() { if (gbIsHellfire) { - LoadMaskedArt("ui_art\\hf_logo2.pcx", &ArtLogos[LOGO_MED], 16); + ArtLogos[LOGO_MED] = LoadPcxAssetAsCel("ui_art\\hf_logo2.pcx", /*numFrames=*/16); } else { - LoadMaskedArt("ui_art\\smlogo.pcx", &ArtLogos[LOGO_MED], 15); + ArtLogos[LOGO_MED] = LoadPcxAssetAsCel("ui_art\\smlogo.pcx", /*numFrames=*/15, /*generateFrameHeaders=*/false, /*transparentColorIndex=*/250); } - LoadMaskedArt("ui_art\\focus16.pcx", &ArtFocus[FOCUS_SMALL], 8); - LoadMaskedArt("ui_art\\focus.pcx", &ArtFocus[FOCUS_MED], 8); - LoadMaskedArt("ui_art\\focus42.pcx", &ArtFocus[FOCUS_BIG], 8); + ArtFocus[FOCUS_SMALL] = LoadPcxAssetAsCel("ui_art\\focus16.pcx", /*numFrames=*/8, /*generateFrameHeaders=*/false, /*transparentColorIndex=*/250); + ArtFocus[FOCUS_MED] = LoadPcxAssetAsCel("ui_art\\focus.pcx", /*numFrames=*/8, /*generateFrameHeaders=*/false, /*transparentColorIndex=*/250); + ArtFocus[FOCUS_BIG] = LoadPcxAssetAsCel("ui_art\\focus42.pcx", /*numFrames=*/8, /*generateFrameHeaders=*/false, /*transparentColorIndex=*/250); LoadMaskedArt("ui_art\\cursor.pcx", &ArtCursor, 1, 0); @@ -594,9 +599,9 @@ void UnloadUiGFX() ArtHero.Unload(); ArtCursor.Unload(); for (auto &art : ArtFocus) - art.Unload(); + art = std::nullopt; for (auto &art : ArtLogos) - art.Unload(); + art = std::nullopt; } void UiInitialize() @@ -700,7 +705,8 @@ void UiAddBackground(std::vector> *vecDialog) void UiAddLogo(std::vector> *vecDialog, int size, int y) { SDL_Rect rect = { 0, (Sint16)(UI_OFFSET_Y + y), 0, 0 }; - vecDialog->push_back(std::make_unique(&ArtLogos[size], rect, UiFlags::AlignCenter, /*bAnimated=*/true)); + vecDialog->push_back(std::make_unique( + CelSpriteWithFrameHeight { ArtLogos[size]->sprite, ArtLogos[size]->frameHeight }, rect, UiFlags::AlignCenter, /*bAnimated=*/true)); } void UiFadeIn() @@ -723,6 +729,19 @@ void UiFadeIn() RenderPresent(); } +void DrawCel(CelSpriteWithFrameHeight sprite, Point p) +{ + const Surface &out = Surface(DiabloUiSurface()); + CelDrawTo(out, { p.x, static_cast(p.y + sprite.frameHeight) }, sprite.sprite, 0); +} + +void DrawAnimatedCel(CelSpriteWithFrameHeight sprite, Point p) +{ + const Surface &out = Surface(DiabloUiSurface()); + const int frame = GetAnimationFrame(LoadLE32(sprite.sprite.Data())); + CelDrawTo(out, { p.x, static_cast(p.y + sprite.frameHeight) }, sprite.sprite, frame); +} + void DrawSelector(const SDL_Rect &rect) { int size = FOCUS_SMALL; @@ -730,13 +749,13 @@ void DrawSelector(const SDL_Rect &rect) size = FOCUS_BIG; else if (rect.h >= 30) size = FOCUS_MED; - Art *art = &ArtFocus[size]; + CelSpriteWithFrameHeight sprite { ArtFocus[size]->sprite, ArtFocus[size]->frameHeight }; - int frame = GetAnimationFrame(art->frames); - int y = rect.y + (rect.h - art->h()) / 2; // TODO FOCUS_MED appares higher than the box + // TODO FOCUS_MED appares higher than the box + const int y = rect.y + (rect.h - static_cast(sprite.frameHeight)) / 2; - DrawArt({ rect.x, y }, art, frame); - DrawArt({ rect.x + rect.w - art->w(), y }, art, frame); + DrawAnimatedCel(sprite, { rect.x, y }); + DrawAnimatedCel(sprite, { rect.x + rect.w - sprite.sprite.Width(), y }); } void UiClearScreen() @@ -794,8 +813,7 @@ void Render(const UiImage *uiImage) { int x = uiImage->m_rect.x; if (uiImage->IsCentered() && uiImage->GetArt() != nullptr) { - const int xOffset = GetCenterOffset(uiImage->GetArt()->w(), uiImage->m_rect.w); - x += xOffset; + x += GetCenterOffset(uiImage->GetArt()->w(), uiImage->m_rect.w); } if (uiImage->IsAnimated()) { DrawAnimatedArt(uiImage->GetArt(), { x, uiImage->m_rect.y }); @@ -804,6 +822,20 @@ void Render(const UiImage *uiImage) } } +void Render(const UiImageCel *uiImage) +{ + const CelSpriteWithFrameHeight &sprite = uiImage->GetSprite(); + int x = uiImage->m_rect.x; + if (uiImage->IsCentered()) { + x += GetCenterOffset(sprite.sprite.Width(), uiImage->m_rect.w); + } + if (uiImage->IsAnimated()) { + DrawAnimatedCel(sprite, { x, uiImage->m_rect.y }); + } else { + DrawCel(sprite, { x, uiImage->m_rect.y }); + } +} + void Render(const UiArtTextButton *uiButton) { Rectangle rect { { uiButton->m_rect.x, uiButton->m_rect.y }, { uiButton->m_rect.w, uiButton->m_rect.h } }; @@ -886,6 +918,9 @@ void RenderItem(UiItemBase *item) case UiType::Image: Render(static_cast(item)); break; + case UiType::ImageCel: + Render(static_cast(item)); + break; case UiType::ArtTextButton: Render(static_cast(item)); break; diff --git a/Source/DiabloUI/diabloui.h b/Source/DiabloUI/diabloui.h index 96a490b5f..317254b02 100644 --- a/Source/DiabloUI/diabloui.h +++ b/Source/DiabloUI/diabloui.h @@ -7,6 +7,7 @@ #include "DiabloUI/art.h" #include "DiabloUI/ui_item.h" +#include "engine/cel_sprite.hpp" #include "player.h" #include "utils/display.h" @@ -66,8 +67,8 @@ struct _uiheroinfo { bool spawned; }; -extern std::array ArtLogos; -extern std::array ArtFocus; +extern std::array, 3> ArtLogos; +extern std::array, 3> ArtFocus; extern Art ArtBackground; extern Art ArtBackgroundWidescreen; extern Art ArtCursor; diff --git a/Source/DiabloUI/title.cpp b/Source/DiabloUI/title.cpp index 4ca3c22b5..b42d114d5 100644 --- a/Source/DiabloUI/title.cpp +++ b/Source/DiabloUI/title.cpp @@ -3,6 +3,7 @@ #include "controls/input.h" #include "controls/menu_controls.h" #include "discord/discord.h" +#include "engine/load_pcx_as_cel.hpp" #include "utils/language.h" namespace devilution { @@ -13,11 +14,13 @@ std::vector> vecTitleScreen; void TitleLoad() { if (gbIsHellfire) { + // This is a 2.4 MiB PCX file without transparency (4.6 MiB as an SDL surface). LoadBackgroundArt("ui_art\\hf_logo1.pcx", 16); + LoadArt("ui_art\\hf_titlew.pcx", &ArtBackgroundWidescreen); } else { LoadBackgroundArt("ui_art\\title.pcx"); - LoadMaskedArt("ui_art\\logo.pcx", &ArtLogos[LOGO_BIG], 15); + ArtLogos[LOGO_BIG] = LoadPcxAssetAsCel("ui_art\\logo.pcx", /*numFrames=*/15, /*generateFrameHeaders=*/false, /*transparentColorIndex=*/250); } } @@ -25,7 +28,7 @@ void TitleFree() { ArtBackground.Unload(); ArtBackgroundWidescreen.Unload(); - ArtLogos[LOGO_BIG].Unload(); + ArtLogos[LOGO_BIG] = std::nullopt; vecTitleScreen.clear(); } @@ -34,6 +37,7 @@ void TitleFree() void UiTitleDialog() { + TitleLoad(); if (gbIsHellfire) { SDL_Rect rect = { 0, UI_OFFSET_Y, 0, 0 }; vecTitleScreen.push_back(std::make_unique(&ArtBackgroundWidescreen, rect, UiFlags::AlignCenter, /*bAnimated=*/true)); @@ -45,7 +49,6 @@ void UiTitleDialog() SDL_Rect rect = { (Sint16)(PANEL_LEFT), (Sint16)(UI_OFFSET_Y + 410), 640, 26 }; vecTitleScreen.push_back(std::make_unique(_("Copyright © 1996-2001 Blizzard Entertainment").c_str(), rect, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver)); } - TitleLoad(); bool endMenu = false; Uint32 timeOut = SDL_GetTicks() + 7000; diff --git a/Source/DiabloUI/ui_item.h b/Source/DiabloUI/ui_item.h index 7ccff17e8..ccdb248b4 100644 --- a/Source/DiabloUI/ui_item.h +++ b/Source/DiabloUI/ui_item.h @@ -7,6 +7,7 @@ #include "DiabloUI/art.h" #include "DiabloUI/ui_flags.hpp" +#include "engine/cel_sprite.hpp" #include "engine/render/text_render.hpp" #include "utils/enum_traits.h" #include "utils/stubs.h" @@ -18,6 +19,7 @@ enum class UiType { ArtText, ArtTextButton, Image, + ImageCel, Button, List, Scrollbar, @@ -129,6 +131,48 @@ private: int frame_; }; +//============================================================================= +class UiImageCel : public UiItemBase { +public: + UiImageCel(CelSpriteWithFrameHeight sprite, SDL_Rect rect, UiFlags flags = UiFlags::None, bool animated = false, int frame = 0) + : UiItemBase(UiType::ImageCel, rect, flags) + , sprite_(sprite) + , animated_(animated) + , frame_(frame) + { + } + + [[nodiscard]] bool IsCentered() const + { + return HasAnyOf(GetFlags(), UiFlags::AlignCenter); + } + + [[nodiscard]] CelSpriteWithFrameHeight GetSprite() const + { + return sprite_; + } + + [[nodiscard]] bool IsAnimated() const + { + return animated_; + } + + [[nodiscard]] int GetFrame() const + { + return frame_; + } + + void SetFrame(int frame) + { + frame_ = frame; + } + +private: + CelSpriteWithFrameHeight sprite_; + bool animated_; + int frame_; +}; + //============================================================================= class UiArtText : public UiItemBase { diff --git a/Source/engine/cel_sprite.hpp b/Source/engine/cel_sprite.hpp index d9d8d8612..6788bdf11 100644 --- a/Source/engine/cel_sprite.hpp +++ b/Source/engine/cel_sprite.hpp @@ -94,6 +94,11 @@ inline CelSprite::CelSprite(const OwnedCelSprite &owned) { } +struct CelSpriteWithFrameHeight { + CelSprite sprite; + unsigned frameHeight; +}; + struct OwnedCelSpriteWithFrameHeight { OwnedCelSprite sprite; unsigned frameHeight;