Browse Source

DiabloUi: Load animated PCX sprites as CEL

Reduces memory usage in menu (and thus the overall allocator pressure)
pull/4518/head
Gleb Mazovetskiy 4 years ago
parent
commit
c2917b1dc8
  1. 69
      Source/DiabloUI/diabloui.cpp
  2. 5
      Source/DiabloUI/diabloui.h
  3. 9
      Source/DiabloUI/title.cpp
  4. 44
      Source/DiabloUI/ui_item.h
  5. 5
      Source/engine/cel_sprite.hpp

69
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<Art, 3> ArtLogos;
std::array<Art, 3> ArtFocus;
// These are stored as PCX but we load them as CEL to reduce memory usage.
std::array<std::optional<OwnedCelSpriteWithFrameHeight>, 3> ArtLogos;
std::array<std::optional<OwnedCelSpriteWithFrameHeight>, 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<std::unique_ptr<UiItemBase>> *vecDialog)
void UiAddLogo(std::vector<std::unique_ptr<UiItemBase>> *vecDialog, int size, int y)
{
SDL_Rect rect = { 0, (Sint16)(UI_OFFSET_Y + y), 0, 0 };
vecDialog->push_back(std::make_unique<UiImage>(&ArtLogos[size], rect, UiFlags::AlignCenter, /*bAnimated=*/true));
vecDialog->push_back(std::make_unique<UiImageCel>(
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<int>(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<int>(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<int>(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<UiImage *>(item));
break;
case UiType::ImageCel:
Render(static_cast<UiImageCel *>(item));
break;
case UiType::ArtTextButton:
Render(static_cast<UiArtTextButton *>(item));
break;

5
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<Art, 3> ArtLogos;
extern std::array<Art, 3> ArtFocus;
extern std::array<std::optional<OwnedCelSpriteWithFrameHeight>, 3> ArtLogos;
extern std::array<std::optional<OwnedCelSpriteWithFrameHeight>, 3> ArtFocus;
extern Art ArtBackground;
extern Art ArtBackgroundWidescreen;
extern Art ArtCursor;

9
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<std::unique_ptr<UiItemBase>> 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<UiImage>(&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<UiArtText>(_("Copyright © 1996-2001 Blizzard Entertainment").c_str(), rect, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiSilver));
}
TitleLoad();
bool endMenu = false;
Uint32 timeOut = SDL_GetTicks() + 7000;

44
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 {

5
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;

Loading…
Cancel
Save