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.
This commit is contained in:
parent
1519e995cf
commit
a5e1fa5bbe
8 changed files with 192 additions and 28 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Normal file
61
Source/utils/file_name_generator.hpp
Normal file
|
|
@ -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
Normal file
60
Source/utils/static_vector.hpp
Normal file
|
|
@ -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…
Add table
Add a link
Reference in a new issue