Skip to content

Commit

Permalink
Slightly optimize loading same-sprite monsters
Browse files Browse the repository at this point in the history
For monsters with the same sprite, load the sprite from the file system
only once.

Example:

```
Loaded monster graphics: SkelAxe\SklAx    599 KiB   x1
Loaded monster graphics: SkelBow\SklBw    623 KiB   x1
Loaded monster graphics: Scav\Scav        328 KiB   x2
Loaded monster graphics: Sneak\Sneak      765 KiB   x1
Loaded monster graphics: Bat\Bat          287 KiB   x1
Loaded monster graphics: Golem\Golem      765 KiB   x1
 Total monster graphics:                 3369 KiB 3698 KiB
```
  • Loading branch information
glebm committed Oct 11, 2023
1 parent 706010e commit c2a195a
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 174 deletions.
1 change: 1 addition & 0 deletions Source/levels/gendung.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ void LoadDungeonBase(const char *path, Point spawn, int floorId, int dirtId)
LoadTransparency(dunData.get());

SetMapMonsters(dunData.get(), Point(0, 0).megaToWorld());
InitAllMonsterGFX();
SetMapObjects(dunData.get(), 0, 0);
}

Expand Down
343 changes: 204 additions & 139 deletions Source/monstdat.cpp

Large diffs are not rendered by default.

71 changes: 70 additions & 1 deletion Source/monstdat.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,73 @@ enum class MonsterAvailability : uint8_t {
Retail,
};

enum class MonsterSpriteId : uint8_t {
Zombie = 0, // "Zombie\\Zombie"
FallenSpear, // "FalSpear\\Phall"
FallenSword, // "FalSword\\Fall"
SkeletonAxe, // "SkelAxe\\SklAx"
SkeletonBow, // "SkelBow\\SklBw"
SkeletonCaptain, // "SkelSd\\SklSr"
SkeletonDemon, // "Demskel\\Demskl"
SkeletonKing, // "SKing\\SKing"
Scavenger, // "Scav\\Scav"
InvisibleLord, // "TSneak\\TSneak"
Hidden, // "Sneak\\Sneak"
GoatMace, // "GoatMace\\Goat"
GoatBow, // "GoatBow\\GoatB"
GoatLord, // "GoatLord\\GoatL"
Bat, // "Bat\\Bat"
AcidBeast, // "Acid\\Acid"
Overlord, // "Fat\\Fat"
Butcher, // "FatC\\FatC"
Wyrm, // "Worm\\Worm"
MagmaDemon, // "Magma\\Magma"
HornedDemon, // "Rhino\\Rhino"
StormRider, // "Thin\\Thin"
Incinerator, // "Fireman\\FireM"
DevilKinBrute, // "BigFall\\Fallg"
Gargoyle, // "Gargoyle\\Gargo"
Slayer, // "Mega\\Mega"
Viper, // "Snake\\Snake"
BlackKnight, // "Black\\Black"
Shredded, // "Unrav\\Unrav"
Succubus, // "Succ\\Scbs"
Counselor, // "Mage\\Mage"
Golem, // "Golem\\Golem"
Diablo, // "Diablo\\Diablo"
ArchLitch, // "DarkMage\\Dmage"
Hellboar, // "Fork\\Fork"
Stinger, // "Scorp\\Scorp"
Psychorb, // "Eye\\Eye"
Arachnon, // "Spider\\Spider"
HorkSpawn, // "Spawn\\Spawn"
Venomtail, // "WScorp\\WScorp"
Necromorb, // "Eye2\\Eye2"
SpiderLord, // "bSpidr\\bSpidr"
Lashworm, // "Clasp\\Clasp"
Torchant, // "AntWorm\\Worm"
HorkDemon, // "HorkD\\HorkD"
HellBug, // "Hellbug\\Hellbg"
Gravedigger, // "Gravdg\\Gravdg"
Rat, // "Rat\\Rat"
Firebat, // "Hellbat\\Helbat"
Lich, // "Lich\\Lich"
CryptDemon, // "Bubba\\Bubba"
Hellbat, // "Hellbat2\\bhelbt"
ArchLich, // "Lich2\\Lich2"
Biclops, // "Byclps\\Byclps"
FleshThing, // "Flesh\\Flesh"
Reaper, // "Reaper\\Reap"
NaKrul, // "Nkr\\Nkr"
FIRST = Zombie,
LAST = NaKrul
};

struct MonsterData {
const char *name;
const char *assetsSuffix;
const char *soundSuffix;
const char *trnFile;
MonsterSpriteId spriteId;
MonsterAvailability availability;
uint16_t width;
uint16_t image;
Expand Down Expand Up @@ -129,6 +191,13 @@ struct MonsterData {
/** Using monster_treasure */
uint16_t treasure;
uint16_t exp;

[[nodiscard]] const char *spritePath() const;

[[nodiscard]] const char *soundPath() const
{
return soundSuffix != nullptr ? soundSuffix : spritePath();
}
};

enum _monster_id : int16_t {
Expand Down
111 changes: 81 additions & 30 deletions Source/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
#include "utils/cl2_to_clx.hpp"
#include "utils/file_name_generator.hpp"
#include "utils/language.h"
#include "utils/log.hpp"
#include "utils/static_vector.hpp"
#include "utils/str_cat.hpp"
#include "utils/utf8.hpp"

Expand Down Expand Up @@ -100,12 +102,12 @@ size_t GetNumAnims(const MonsterData &monsterData)
void InitMonsterTRN(CMonster &monst)
{
char path[64];
*BufCopy(path, "monsters\\", monst.data->trnFile, ".trn") = '\0';
*BufCopy(path, "monsters\\", monst.data().trnFile, ".trn") = '\0';
std::array<uint8_t, 256> colorTranslations;
LoadFileInMem(path, colorTranslations);
std::replace(colorTranslations.begin(), colorTranslations.end(), 255, 0);

const size_t numAnims = GetNumAnims(*monst.data);
const size_t numAnims = GetNumAnims(monst.data());
for (size_t i = 0; i < numAnims; i++) {
if (i == 1 && IsAnyOf(monst.type, MT_COUNSLR, MT_MAGISTR, MT_CABALIST, MT_ADVOCATE)) {
continue;
Expand Down Expand Up @@ -409,8 +411,19 @@ size_t AddMonsterType(_monster_id type, placeflag placeflag)
if (typeIndex == LevelMonsterTypeCount) {
LevelMonsterTypeCount++;
monsterType.type = type;
monstimgtot += MonstersData[type].image;
InitMonsterGFX(monsterType);
const MonsterData &monsterData = MonstersData[type];
monstimgtot += monsterData.image;

const size_t numAnims = GetNumAnims(monsterData);
for (size_t i = 0; i < numAnims; ++i) {
AnimStruct &anim = monsterType.anims[i];
anim.frames = monsterData.frames[i];
if (monsterData.frames[i] != 0) {
anim.rate = monsterData.rate[i];
anim.width = monsterData.width;
}
}

InitMonsterSND(monsterType);
}

Expand Down Expand Up @@ -3102,6 +3115,19 @@ bool UpdateModeStance(Monster &monster)
}
}

MonsterSpritesData LoadMonsterSpritesData(const MonsterData &monsterData)
{
MonsterSpritesData result;
result.data = MultiFileLoader<MonsterSpritesData::MaxAnims> {}(
GetNumAnims(monsterData),
FileNameWithCharAffixGenerator({ "Monsters\\", monsterData.spritePath() }, ".CL2", Animletter),
result.offsets.data(),
[&monsterData](size_t index) {
return monsterData.frames[index] != 0;
});
return result;
}

} // namespace

void InitTRNForUniqueMonster(Monster &monster)
Expand Down Expand Up @@ -3321,7 +3347,7 @@ void InitMonsterSND(CMonster &monsterType)
};

const MonsterData &data = MonstersData[monsterType.type];
std::string_view soundSuffix = data.soundSuffix != nullptr ? data.soundSuffix : data.assetsSuffix;
std::string_view soundSuffix = data.soundPath();

for (int i = 0; i < 4; i++) {
std::string_view prefix = prefixes[i];
Expand All @@ -3336,22 +3362,18 @@ void InitMonsterSND(CMonster &monsterType)
}
}

void InitMonsterGFX(CMonster &monsterType)
void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData)
{
const _monster_id mtype = monsterType.type;
const MonsterData &monsterData = MonstersData[mtype];
const size_t numAnims = GetNumAnims(monsterData);
const auto hasAnim = [&monsterData](size_t index) {
return monsterData.frames[index] != 0;
};
constexpr size_t MaxAnims = 6;
std::array<uint32_t, MaxAnims + 1> animOffsets;
if (!HeadlessMode) {
monsterType.animData = MultiFileLoader<MaxAnims> {}(
numAnims,
FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, DEVILUTIONX_CL2_EXT, Animletter),
animOffsets.data(),
hasAnim);
if (spritesData.data == nullptr)
spritesData = LoadMonsterSpritesData(monsterData);
monsterType.animData = std::move(spritesData.data);
}

#ifndef UNPACKED_MPQS
Expand All @@ -3362,48 +3384,41 @@ void InitMonsterGFX(CMonster &monsterType)
for (size_t i = 0, j = 0; i < numAnims; ++i) {
if (!hasAnim(i))
continue;
const uint32_t begin = animOffsets[j];
const uint32_t end = animOffsets[j + 1];
const uint32_t begin = spritesData.offsets[j];
const uint32_t end = spritesData.offsets[j + 1];
clxData.emplace_back();
Cl2ToClx(reinterpret_cast<uint8_t *>(&monsterType.animData[begin]), end - begin,
PointerOrValue<uint16_t> { monsterData.width }, clxData.back());
animOffsets[j] = accumulatedSize;
spritesData.offsets[j] = accumulatedSize;
accumulatedSize += clxData.back().size();
++j;
}
animOffsets[clxData.size()] = accumulatedSize;
spritesData.offsets[clxData.size()] = accumulatedSize;
monsterType.animData = nullptr;
monsterType.animData = std::unique_ptr<std::byte[]>(new std::byte[accumulatedSize]);
for (size_t i = 0; i < clxData.size(); ++i) {
memcpy(&monsterType.animData[animOffsets[i]], clxData[i].data(), clxData[i].size());
memcpy(&monsterType.animData[spritesData.offsets[i]], clxData[i].data(), clxData[i].size());
}
}
#endif

for (size_t i = 0, j = 0; i < numAnims; ++i) {
AnimStruct &anim = monsterType.anims[i];
if (!hasAnim(i)) {
anim.frames = 0;
if (!hasAnim(i))
continue;
}
AnimStruct &anim = monsterType.anims[i];
anim.frames = monsterData.frames[i];
anim.rate = monsterData.rate[i];
anim.width = monsterData.width;
if (!HeadlessMode) {
const uint32_t begin = animOffsets[j];
const uint32_t end = animOffsets[j + 1];
const uint32_t begin = spritesData.offsets[j];
const uint32_t end = spritesData.offsets[j + 1];
auto spritesData = reinterpret_cast<uint8_t *>(&monsterType.animData[begin]);
const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(spritesData, end - begin);
anim.sprites = ClxSpriteListOrSheet { spritesData, numLists };
}
++j;
}

monsterType.data = &monsterData;

if (HeadlessMode)
return;

if (monsterData.trnFile != nullptr) {
InitMonsterTRN(monsterType);
}
Expand Down Expand Up @@ -3451,6 +3466,39 @@ void InitMonsterGFX(CMonster &monsterType)
GetMissileSpriteData(MissileGraphicID::DiabloApocalypseBoom).LoadGFX();
}

void InitAllMonsterGFX()
{
if (HeadlessMode)
return;

using LevelMonsterTypeIndices = StaticVector<uint8_t, 8>;
std::array<LevelMonsterTypeIndices, enum_size<MonsterSpriteId>::value> monstersBySprite;
for (size_t i = 0; i < LevelMonsterTypeCount; ++i) {
monstersBySprite[static_cast<size_t>(LevelMonsterTypes[i].data().spriteId)].emplace_back(i);
}
uint32_t totalUniqueBytes = 0;
uint32_t totalBytes = 0;
for (const LevelMonsterTypeIndices &monsterTypes : monstersBySprite) {
if (monsterTypes.empty())
continue;
CMonster &firstMonster = LevelMonsterTypes[monsterTypes[0]];
if (firstMonster.animData != nullptr)
continue;
MonsterSpritesData spritesData = LoadMonsterSpritesData(firstMonster.data());
const size_t spritesDataSize = spritesData.offsets[GetNumAnims(firstMonster.data())];
for (size_t i = 1; i < monsterTypes.size(); ++i) {
MonsterSpritesData spritesDataCopy { std::unique_ptr<std::byte[]> { new std::byte[spritesDataSize] }, spritesData.offsets };
memcpy(spritesDataCopy.data.get(), spritesData.data.get(), spritesDataSize);
InitMonsterGFX(LevelMonsterTypes[monsterTypes[i]], std::move(spritesDataCopy));
}
LogVerbose("Loaded monster graphics: {:15s} {:>4d} KiB x{:d}", firstMonster.data().spritePath(), spritesDataSize / 1024, monsterTypes.size());
totalUniqueBytes += spritesDataSize;
totalBytes += spritesDataSize * monsterTypes.size();
InitMonsterGFX(firstMonster, std::move(spritesData));
}
LogVerbose(" Total monster graphics: {:>4d} KiB {:>4d} KiB", totalUniqueBytes / 1024, totalBytes / 1024);
}

void WeakenNaKrul()
{
if (currlevel != 24 || static_cast<size_t>(UberDiabloMonsterIndex) >= ActiveMonsterCount)
Expand Down Expand Up @@ -3530,6 +3578,8 @@ void InitMonsters()
DoUnVision(trigs[i].position + Displacement { s, t }, 15);
}
}

InitAllMonsterGFX();
}

void SetMapMonsters(const uint16_t *dunData, Point startPosition)
Expand Down Expand Up @@ -4037,6 +4087,7 @@ void FreeMonsters()
{
for (CMonster &monsterType : LevelMonsterTypes) {
monsterType.animData = nullptr;
monsterType.corpseId = 0;
for (AnimStruct &animData : monsterType.anims) {
animData.sprites = std::nullopt;
}
Expand Down Expand Up @@ -4166,7 +4217,7 @@ void SyncMonsterAnim(Monster &monster)
#ifdef _DEBUG
// fix for saves with debug monsters having type originally not on the level
CMonster &monsterType = LevelMonsterTypes[monster.levelType];
if (monsterType.data == nullptr) {
if (monsterType.corpseId == 0) {
InitMonsterGFX(monsterType);
monsterType.corpseId = 1;
}
Expand Down
19 changes: 15 additions & 4 deletions Source/monster.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,26 @@ enum class MonsterSound : uint8_t {
Special
};

struct MonsterSpritesData {
static constexpr size_t MaxAnims = 6;
std::unique_ptr<std::byte[]> data;
std::array<uint32_t, MaxAnims + 1> offsets;
};

struct CMonster {
std::unique_ptr<std::byte[]> animData;
AnimStruct anims[6];
std::unique_ptr<TSnd> sounds[4][2];
const MonsterData *data;

_monster_id type;
/** placeflag enum as a flags*/
uint8_t placeFlags;
int8_t corpseId;
int8_t corpseId = 0;

const MonsterData &data() const
{
return MonstersData[type];
}

/**
* @brief Returns AnimStruct for specified graphic
Expand Down Expand Up @@ -313,7 +323,7 @@ struct Monster { // note: missing field _mAFNum

const MonsterData &data() const
{
return *type().data;
return type().data();
}

/**
Expand Down Expand Up @@ -466,7 +476,8 @@ void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t
void InitLevelMonsters();
void GetLevelMTypes();
void InitMonsterSND(CMonster &monsterType);
void InitMonsterGFX(CMonster &monsterType);
void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData = {});
void InitAllMonsterGFX();
void WeakenNaKrul();
void InitGolems();
void InitMonsters();
Expand Down
2 changes: 2 additions & 0 deletions Source/pfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ void CreateDetailDiffs(std::string_view prefix, std::string_view memoryMapFile,
size_t readBytes = SDL_RWsize(handle);
std::unique_ptr<std::byte[]> memoryMapFileData { new std::byte[readBytes] };
SDL_RWread(handle, memoryMapFileData.get(), readBytes, 1);
SDL_RWclose(handle);

const std::string_view buffer(reinterpret_cast<const char *>(memoryMapFileData.get()), readBytes);

std::unordered_map<std::string, CompareCounter> counter;
Expand Down
5 changes: 5 additions & 0 deletions Source/utils/static_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class StaticVector {
return size_;
}

[[nodiscard]] bool empty() const
{
return size_ == 0;
}

[[nodiscard]] T &back()
{
return (*this)[size_ - 1];
Expand Down

0 comments on commit c2a195a

Please sign in to comment.