Browse Source

Load missile frames into a single buffer

Previously, the memory for each frame was allocated separately.

Changes it to allocate a single buffer for all the frames.

This has the following advantages:

1. Less bookkeeping overhead in the allocator.
2. Less alignment overhead (allocator results are max-aligned by default).

We can follow this up with a similar treatment for other multi-file
animations.
pull/4017/head
Gleb Mazovetskiy 4 years ago
parent
commit
a5e1fa5bbe
  1. 2
      Source/dead.cpp
  2. 34
      Source/engine/load_file.hpp
  3. 34
      Source/misdat.cpp
  4. 20
      Source/misdat.h
  5. 4
      Source/missiles.cpp
  6. 5
      Source/scrollrt.cpp
  7. 61
      Source/utils/file_name_generator.hpp
  8. 60
      Source/utils/static_vector.hpp

2
Source/dead.cpp

@ -48,7 +48,7 @@ void InitCorpses()
nd++; // Unused blood spatter
for (auto &corpse : Corpses[nd].data)
corpse = MissileSpriteData[MFILE_SHATTER1].animData[0].get();
corpse = MissileSpriteData[MFILE_SHATTER1].GetFirstFrame();
Corpses[nd].frame = 12;
Corpses[nd].width = 128;

34
Source/engine/load_file.hpp

@ -8,6 +8,7 @@
#include "appfat.h"
#include "diablo.h"
#include "engine/assets.hpp"
#include "utils/static_vector.hpp"
#include "utils/stdcompat/cstddef.hpp"
namespace devilution {
@ -100,4 +101,37 @@ std::unique_ptr<T[]> LoadFileInMem(const char *path, std::size_t *numRead = null
return buf;
}
/**
* @brief Reads multiple files into a single buffer
*
* @tparam MaxFiles maximum number of files
*/
template <size_t MaxFiles>
struct MultiFileLoader {
/**
* @param numFiles number of files to read
* @param pathFn a function that returns the path for the given index
* @param outOffsets a buffer index for the start of each file will be written here
* @return std::unique_ptr<byte[]> the buffer with all the files
*/
template <typename PathFn>
[[nodiscard]] std::unique_ptr<byte[]> operator()(size_t numFiles, PathFn &&pathFn, uint32_t *outOffsets)
{
StaticVector<SFile, MaxFiles> files;
StaticVector<uint32_t, MaxFiles> sizes;
size_t totalSize = 0;
for (size_t i = 0; i < numFiles; ++i) {
const size_t size = files.emplace_back(pathFn(i)).Size();
sizes.emplace_back(static_cast<uint32_t>(size));
outOffsets[i] = static_cast<uint32_t>(totalSize);
totalSize += size;
}
std::unique_ptr<byte[]> buf { new byte[totalSize] };
for (size_t i = 0; i < numFiles; ++i) {
files[i].Read(&buf[outOffsets[i]], sizes[i]);
}
return buf;
}
};
} // namespace devilution

34
Source/misdat.cpp

@ -8,6 +8,7 @@
#include "engine/cel_header.hpp"
#include "engine/load_file.hpp"
#include "missiles.h"
#include "utils/file_name_generator.hpp"
namespace devilution {
@ -140,13 +141,13 @@ MissileFileData MissileSpriteData[] = {
{ "Bluexfr", MFILE_BLUEXFR, 1, MissileDataFlags::None, { 0 }, { 19 }, 160, 48 },
{ "Bluexbk", MFILE_BLUEXBK, 1, MissileDataFlags::None, { 0 }, { 19 }, 160, 48 },
{ "Manashld", MFILE_MANASHLD, 1, MissileDataFlags::NotAnimated, { 0 }, { 1 }, 96, 16 },
{ nullptr, MFILE_BLOOD, 4, MissileDataFlags::None, { 0 }, { 15 }, 96, 16 },
{ nullptr, MFILE_BONE, 3, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 },
{ nullptr, MFILE_METLHIT, 3, MissileDataFlags::None, { 2 }, { 10 }, 96, 16 },
{ {}, MFILE_BLOOD, 4, MissileDataFlags::None, { 0 }, { 15 }, 96, 16 },
{ {}, MFILE_BONE, 3, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 },
{ {}, MFILE_METLHIT, 3, MissileDataFlags::None, { 2 }, { 10 }, 96, 16 },
{ "Farrow", MFILE_FARROW, 16, MissileDataFlags::None, { 0 }, { 4 }, 96, 16 },
{ "Doom", MFILE_DOOM, 9, MissileDataFlags::MonsterOwned, { 1 }, { 15 }, 96, 16 },
{ nullptr, MFILE_0F, 1, MissileDataFlags::MonsterOwned, { 0 }, { 0 }, 0, 0 },
{ nullptr, MFILE_BLODBUR, 2, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 },
{ {}, MFILE_0F, 1, MissileDataFlags::MonsterOwned, { 0 }, { 0 }, 0, 0 },
{ {}, MFILE_BLODBUR, 2, MissileDataFlags::None, { 2 }, { 8 }, 128, 32 },
{ "Newexp", MFILE_NEWEXP, 1, MissileDataFlags::None, { 1 }, { 15 }, 96, 16 },
{ "Shatter1", MFILE_SHATTER1, 1, MissileDataFlags::None, { 1 }, { 12 }, 128, 32 },
{ "Bigexp", MFILE_BIGEXP, 1, MissileDataFlags::None, { 0 }, { 15 }, 160, 48 },
@ -160,11 +161,11 @@ MissileFileData MissileSpriteData[] = {
{ "Holy", MFILE_HOLY, 16, MissileDataFlags::None, { 1, 0 }, { 14 }, 96, 16 },
{ "Holyexpl", MFILE_HOLYEXPL, 1, MissileDataFlags::None, { 0 }, { 8 }, 160, 48 },
{ "Larrow", MFILE_LARROW, 16, MissileDataFlags::None, { 0 }, { 4 }, 96, 16 },
{ nullptr, MFILE_FIRARWEX, 1, MissileDataFlags::None, { 0 }, { 6 }, 64, 0 },
{ {}, MFILE_FIRARWEX, 1, MissileDataFlags::None, { 0 }, { 6 }, 64, 0 },
{ "Acidbf", MFILE_ACIDBF, 16, MissileDataFlags::MonsterOwned, { 0 }, { 8 }, 96, 16 },
{ "Acidspla", MFILE_ACIDSPLA, 1, MissileDataFlags::MonsterOwned, { 0 }, { 8 }, 96, 16 },
{ "Acidpud", MFILE_ACIDPUD, 2, MissileDataFlags::MonsterOwned, { 0 }, { 9, 4 }, 96, 16 },
{ nullptr, MFILE_ETHRSHLD, 1, MissileDataFlags::None, { 0 }, { 1 }, 96, 16 },
{ {}, MFILE_ETHRSHLD, 1, MissileDataFlags::None, { 0 }, { 1 }, 96, 16 },
{ "Firerun", MFILE_FIRERUN, 8, MissileDataFlags::None, { 1 }, { 12 }, 96, 16 },
{ "Ressur1", MFILE_RESSUR1, 1, MissileDataFlags::None, { 0 }, { 16 }, 96, 16 },
{ "Sklball", MFILE_SKLBALL, 9, MissileDataFlags::None, { 1 }, { 16, 16, 16, 16, 16, 16, 16, 16, 8 }, 96, 16 },
@ -189,7 +190,7 @@ MissileFileData MissileSpriteData[] = {
{ "ms_blb", MFILE_BONEDEMON, 16, MissileDataFlags::MonsterOwned, { 0 }, { 15 }, 96, 8 },
{ "ex_ora1", MFILE_EXORA1, 1, MissileDataFlags::MonsterOwned, { 0 }, { 13 }, 96, -12 },
{ "ex_blu3", MFILE_EXBL3, 1, MissileDataFlags::MonsterOwned, { 0 }, { 7 }, 292, 114 },
{ "", MFILE_NONE, 0, MissileDataFlags::None, { }, { }, 0, 0 },
{ {}, MFILE_NONE, 0, MissileDataFlags::None, { }, { }, 0, 0 },
// clang-format on
};
@ -214,7 +215,7 @@ std::array<T, 16> maybeAutofill(std::initializer_list<T> list)
} // namespace
MissileFileData::MissileFileData(const char *name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags,
MissileFileData::MissileFileData(string_view name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags,
std::initializer_list<uint8_t> animDelay, std::initializer_list<uint8_t> animLen,
int16_t animWidth, int16_t animWidth2)
: name(name)
@ -230,21 +231,18 @@ MissileFileData::MissileFileData(const char *name, uint8_t animName, uint8_t ani
void MissileFileData::LoadGFX()
{
if (animData[0] != nullptr)
if (animData != nullptr)
return;
if (name == nullptr)
if (name.empty())
return;
char pszName[256];
FileNameGenerator pathGenerator({ "Missiles\\", name }, ".CL2");
if (animFAmt == 1) {
sprintf(pszName, "Missiles\\%s.CL2", name);
animData[0] = LoadFileInMem(pszName);
animData = LoadFileInMem(pathGenerator());
frameOffsets[0] = 0;
} else {
for (unsigned i = 0; i < animFAmt; i++) {
sprintf(pszName, "Missiles\\%s%u.CL2", name, i + 1);
animData[i] = LoadFileInMem(pszName);
}
animData = MultiFileLoader<16> {}(animFAmt, pathGenerator, &frameOffsets[0]);
}
}

20
Source/misdat.h

@ -11,6 +11,7 @@
#include "effects.h"
#include "engine.h"
#include "utils/stdcompat/cstddef.hpp"
#include "utils/stdcompat/string_view.hpp"
namespace devilution {
@ -134,7 +135,7 @@ enum class MissileDataFlags {
};
struct MissileFileData {
const char *name;
string_view name;
uint8_t animName;
uint8_t animFAmt;
MissileDataFlags flags;
@ -142,17 +143,28 @@ struct MissileFileData {
std::array<uint8_t, 16> animLen = {};
int16_t animWidth;
int16_t animWidth2;
std::array<std::unique_ptr<byte[]>, 16> animData;
std::unique_ptr<byte[]> animData;
std::array<uint32_t, 16> frameOffsets;
MissileFileData(const char *name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags,
MissileFileData(string_view name, uint8_t animName, uint8_t animFAmt, MissileDataFlags flags,
std::initializer_list<uint8_t> animDelay, std::initializer_list<uint8_t> animLen,
int16_t animWidth, int16_t animWidth2);
void LoadGFX();
[[nodiscard]] const byte *GetFirstFrame() const
{
return animData.get();
}
[[nodiscard]] const byte *GetFrame(size_t i) const
{
return &animData[frameOffsets[i]];
}
void FreeGFX()
{
animData = {};
animData = nullptr;
}
};

4
Source/missiles.cpp

@ -642,7 +642,7 @@ void SetMissAnim(Missile &missile, int animtype)
missile._miAnimType = animtype;
missile._miAnimFlags = MissileSpriteData[animtype].flags;
missile._miAnimData = MissileSpriteData[animtype].animData[dir].get();
missile._miAnimData = MissileSpriteData[animtype].GetFrame(static_cast<size_t>(dir));
missile._miAnimDelay = MissileSpriteData[animtype].animDelay[dir];
missile._miAnimLen = MissileSpriteData[animtype].animLen[dir];
missile._miAnimWidth = MissileSpriteData[animtype].animWidth;
@ -4164,7 +4164,7 @@ void ProcessMissiles()
void missiles_process_charge()
{
for (auto &missile : Missiles) {
missile._miAnimData = MissileSpriteData[missile._miAnimType].animData[missile._mimfnum].get();
missile._miAnimData = MissileSpriteData[missile._miAnimType].GetFrame(missile._mimfnum);
if (missile._mitype != MIS_RHINO)
continue;

5
Source/scrollrt.cpp

@ -310,8 +310,7 @@ void DrawMissilePrivate(const Surface &out, const Missile &missile, Point target
return;
}
int nCel = missile._miAnimFrame;
const auto *frameTable = reinterpret_cast<const uint32_t *>(missile._miAnimData);
int frames = SDL_SwapLE32(frameTable[0]);
const int frames = LoadLE32(missile._miAnimData);
if (nCel < 1 || frames > 50 || nCel > frames) {
Log("Draw Missile 2: frame {} of {}, missile type=={}", nCel, frames, missile._mitype);
return;
@ -458,7 +457,7 @@ void DrawPlayerIconHelper(const Surface &out, int pnum, missile_graphic_id missi
position.x += CalculateWidth2(Players[pnum].AnimInfo.pCelSprite->Width()) - MissileSpriteData[missileGraphicId].animWidth2;
int width = MissileSpriteData[missileGraphicId].animWidth;
byte *pCelBuff = MissileSpriteData[missileGraphicId].animData[0].get();
const byte *pCelBuff = MissileSpriteData[missileGraphicId].GetFirstFrame();
CelSprite cel { pCelBuff, width };

61
Source/utils/file_name_generator.hpp

@ -0,0 +1,61 @@
#pragma once
#include <cstring>
#include <initializer_list>
#include <fmt/format.h>
#include "utils/stdcompat/string_view.hpp"
namespace devilution {
/**
* @brief Generates file names from prefixes, a suffix, and an index.
*
* @example FileNameGenerator f({"a/", "b"}, ".txt", 1);
* f() // "a/b.txt"
* f(0) // "a/b1.txt"
* f(1) // "a/b2.txt"
*/
class FileNameGenerator {
public:
FileNameGenerator(std::initializer_list<string_view> prefixes, string_view suffix, unsigned min = 1)
: suffix_(suffix)
, min_(min)
, prefixEnd_(Append(buf_, prefixes))
{
}
const char *operator()() const
{
*Append(prefixEnd_, suffix_) = '\0';
return buf_;
}
const char *operator()(size_t i) const
{
*Append(fmt::format_to(prefixEnd_, "{}", static_cast<unsigned>(min_ + i)), suffix_) = '\0';
return buf_;
}
private:
static char *Append(char *buf, std::initializer_list<string_view> strings)
{
for (string_view str : strings)
buf = Append(buf, str);
return buf;
}
static char *Append(char *buf, string_view str)
{
memcpy(buf, str.data(), str.size());
return buf + str.size();
}
string_view suffix_;
unsigned min_;
char *prefixEnd_;
char buf_[256];
};
} // namespace devilution

60
Source/utils/static_vector.hpp

@ -0,0 +1,60 @@
#pragma once
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include "appfat.h"
namespace devilution {
/**
* @brief A stack-allocated vector with a fixed capacity.
*
* @tparam T element type.
* @tparam N capacity.
*/
template <class T, size_t N>
class StaticVector {
public:
template <typename... Args>
T &emplace_back(Args &&...args) // NOLINT(readability-identifier-naming)
{
assert(size_ < N);
::new (&data_[size_]) T(std::forward<Args>(args)...);
#if __cplusplus >= 201703L
T &result = *std::launder(reinterpret_cast<T *>(&data_[size_]));
#else
T &result = *reinterpret_cast<T *>(&data_[size_]);
#endif
++size_;
return result;
}
const T &operator[](std::size_t pos) const
{
#if __cplusplus >= 201703L
return *std::launder(reinterpret_cast<const T *>(&data_[pos]));
#else
return *reinterpret_cast<const T *>(&data_[pos]);
#endif
}
~StaticVector()
{
for (std::size_t pos = 0; pos < size_; ++pos) {
#if __cplusplus >= 201703L
std::destroy_at(std::launder(reinterpret_cast<T *>(&data_[pos])));
#else
reinterpret_cast<T *>(&data_[pos])->~T();
#endif
}
}
private:
std::aligned_storage_t<sizeof(T), alignof(T)> data_[N];
std::size_t size_ = 0;
};
} // namespace devilution
Loading…
Cancel
Save