Skip to content

Commit 33f93a9

Browse files
committed
Change: Use custom StaticVector for base64/buffer data
Replaces sources::Vector with sources::Array which holds a StaticVector, a statically sized vector which cannot be resized through resize/reserve/emplace_back and doesn't overallocate nor initialize.
1 parent d1112fc commit 33f93a9

File tree

9 files changed

+139
-53
lines changed

9 files changed

+139
-53
lines changed

examples/gl_viewer/gl_viewer.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ bool loadGltf(Viewer* viewer, std::string_view cPath) {
328328

329329
std::visit(fastgltf::visitor {
330330
[](auto& arg) {}, // Covers FilePathWithOffset, BufferView, ... which are all not possible
331-
[&](fastgltf::sources::Vector& vector) {
331+
[&](fastgltf::sources::Array& vector) {
332332
GLuint glBuffer;
333333
glCreateBuffers(1, &glBuffer);
334334
glNamedBufferData(glBuffer, static_cast<int64_t>(buffer.byteLength),
@@ -475,7 +475,7 @@ bool loadImage(Viewer* viewer, fastgltf::Image& image) {
475475
glTextureSubImage2D(texture, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
476476
stbi_image_free(data);
477477
},
478-
[&](fastgltf::sources::Vector& vector) {
478+
[&](fastgltf::sources::Array& vector) {
479479
int width, height, nrChannels;
480480
unsigned char *data = stbi_load_from_memory(vector.bytes.data(), static_cast<int>(vector.bytes.size()), &width, &height, &nrChannels, 4);
481481
glTextureStorage2D(texture, getLevelCount(width, height), GL_RGBA8, width, height);
@@ -491,7 +491,7 @@ bool loadImage(Viewer* viewer, fastgltf::Image& image) {
491491
// We only care about VectorWithMime here, because we specify LoadExternalBuffers, meaning
492492
// all buffers are already loaded into a vector.
493493
[](auto& arg) {},
494-
[&](fastgltf::sources::Vector& vector) {
494+
[&](fastgltf::sources::Array& vector) {
495495
int width, height, nrChannels;
496496
unsigned char* data = stbi_load_from_memory(vector.bytes.data() + bufferView.byteOffset, static_cast<int>(bufferView.byteLength), &width, &height, &nrChannels, 4);
497497
glTextureStorage2D(texture, getLevelCount(width, height), GL_RGBA8, width, height);

include/fastgltf/base64.hpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
#include <cstddef>
3131
#include <cstdint>
3232
#include <string_view>
33-
#include <vector>
33+
34+
#include <fastgltf/types.hpp>
3435

3536
#ifdef _MSC_VER
3637
#pragma warning(push) // attribute 'x' is not recognized
@@ -73,17 +74,17 @@ namespace fastgltf::base64 {
7374
void sse4_decode_inplace(std::string_view encoded, std::uint8_t* output, std::size_t padding);
7475
void avx2_decode_inplace(std::string_view encoded, std::uint8_t* output, std::size_t padding);
7576

76-
[[nodiscard]] std::vector<std::uint8_t> sse4_decode(std::string_view encoded);
77-
[[nodiscard]] std::vector<std::uint8_t> avx2_decode(std::string_view encoded);
77+
[[nodiscard]] StaticVector<std::uint8_t> sse4_decode(std::string_view encoded);
78+
[[nodiscard]] StaticVector<std::uint8_t> avx2_decode(std::string_view encoded);
7879
#elif defined(FASTGLTF_IS_A64)
7980
void neon_decode_inplace(std::string_view encoded, std::uint8_t* output, std::size_t padding);
80-
[[nodiscard]] std::vector<std::uint8_t> neon_decode(std::string_view encoded);
81+
[[nodiscard]] StaticVector<std::uint8_t> neon_decode(std::string_view encoded);
8182
#endif
8283
void fallback_decode_inplace(std::string_view encoded, std::uint8_t* output, std::size_t padding);
8384
void decode_inplace(std::string_view encoded, std::uint8_t* output, std::size_t padding);
8485

85-
[[nodiscard]] std::vector<std::uint8_t> fallback_decode(std::string_view encoded);
86-
[[nodiscard]] std::vector<std::uint8_t> decode(std::string_view encoded);
86+
[[nodiscard]] StaticVector<std::uint8_t> fallback_decode(std::string_view encoded);
87+
[[nodiscard]] StaticVector<std::uint8_t> decode(std::string_view encoded);
8788
} // namespace fastgltf::base64
8889

8990
#ifdef _MSC_VER

include/fastgltf/tools.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ struct DefaultBufferDataAdapter {
311311
assert(false && "Tried accessing a buffer with no data, likely because no buffers were loaded. Perhaps you forgot to specify the LoadExternalBuffers option?");
312312
return nullptr;
313313
},
314-
[&](const sources::Vector& vec) {
314+
[&](const sources::Array& vec) {
315315
return reinterpret_cast<const std::byte*>(vec.bytes.data());
316316
},
317317
[&](const sources::ByteView& bv) {

include/fastgltf/types.hpp

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,84 @@ namespace fastgltf {
457457
#pragma endregion
458458

459459
#pragma region Containers
460+
/**
461+
* A static vector which cannot be resized freely. When constructed, the backing array is allocated once.
462+
*/
463+
template <typename T>
464+
class StaticVector final {
465+
using array_t = T[];
466+
467+
std::unique_ptr<array_t> _array;
468+
std::size_t _size = 0;
469+
470+
public:
471+
using pointer = T*;
472+
using const_pointer = const T*;
473+
using iterator = pointer;
474+
using const_iterator = const_pointer;
475+
476+
explicit StaticVector(std::size_t size) : _size(size), _array(std::move(std::unique_ptr<array_t>(new std::remove_extent_t<array_t>[size]))) {}
477+
478+
StaticVector(const StaticVector& other) {
479+
if (other.size() == 0) {
480+
_array.reset();
481+
_size = 0;
482+
} else {
483+
_array.reset(new std::remove_extent_t<array_t>[other.size()]);
484+
_size = other.size();
485+
}
486+
}
487+
488+
StaticVector(StaticVector&& other) noexcept {
489+
_array = std::move(other._array);
490+
_size = other.size();
491+
}
492+
493+
StaticVector& operator=(StaticVector&& other) noexcept {
494+
_array = std::move(other._array);
495+
_size = other.size();
496+
return *this;
497+
}
498+
499+
[[nodiscard]] pointer data() noexcept {
500+
return &_array.get()[0];
501+
}
502+
503+
[[nodiscard]] const_pointer data() const noexcept {
504+
return &_array.get()[0];
505+
}
506+
507+
[[nodiscard]] std::size_t size() const noexcept {
508+
return _size;
509+
}
510+
511+
[[nodiscard]] std::size_t size_bytes() const noexcept {
512+
return _size * sizeof(T);
513+
}
514+
515+
[[nodiscard]] bool empty() const noexcept {
516+
return _size == 0;
517+
}
518+
519+
[[nodiscard]] iterator begin() noexcept { return data(); }
520+
[[nodiscard]] const_iterator begin() const noexcept { return data(); }
521+
[[nodiscard]] const_iterator cbegin() const noexcept { return data(); }
522+
[[nodiscard]] iterator end() noexcept { return begin() + size(); }
523+
[[nodiscard]] const_iterator end() const noexcept { return begin() + size(); }
524+
[[nodiscard]] const_iterator cend() const noexcept { return begin() + size(); }
525+
526+
bool operator==(const StaticVector<T>& other) const {
527+
if (other.size() != size()) return false;
528+
return std::memcmp(data(), other.data(), size_bytes()) == 0;
529+
}
530+
531+
// This is mostly just here for compatibility and the tests
532+
bool operator==(const std::vector<T>& other) const {
533+
if (other.size() != size()) return false;
534+
return std::memcmp(data(), other.data(), size_bytes()) == 0;
535+
}
536+
};
537+
460538
/*
461539
* The amount of items that the SmallVector can initially store in the storage
462540
* allocated within the object itself.
@@ -1327,8 +1405,8 @@ namespace fastgltf {
13271405
MimeType mimeType;
13281406
};
13291407

1330-
struct Vector {
1331-
std::vector<std::uint8_t> bytes;
1408+
struct Array {
1409+
StaticVector<std::uint8_t> bytes;
13321410
MimeType mimeType;
13331411
};
13341412

@@ -1355,7 +1433,7 @@ namespace fastgltf {
13551433
*
13561434
* @note For buffers, this variant will never hold a sources::BufferView, as only images are able to reference buffer views as a source.
13571435
*/
1358-
using DataSource = std::variant<std::monostate, sources::BufferView, sources::URI, sources::Vector, sources::CustomBuffer, sources::ByteView, sources::Fallback>;
1436+
using DataSource = std::variant<std::monostate, sources::BufferView, sources::URI, sources::Array, sources::CustomBuffer, sources::ByteView, sources::Fallback>;
13591437

13601438
struct AnimationChannel {
13611439
std::size_t samplerIndex;

src/base64.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ namespace fg = fastgltf;
7070

7171
namespace fastgltf::base64 {
7272
using DecodeFunctionInplace = std::function<void(std::string_view, std::uint8_t*, std::size_t)>;
73-
using DecodeFunction = std::function<std::vector<std::uint8_t>(std::string_view)>;
73+
using DecodeFunction = std::function<fg::StaticVector<std::uint8_t>(std::string_view)>;
7474

7575
struct DecodeFunctionGetter {
7676
DecodeFunction func;
@@ -361,11 +361,11 @@ void fg::base64::neon_decode_inplace(std::string_view encoded, std::uint8_t* out
361361
fallback_decode_inplace(encoded.substr(pos, encodedSize), out, padding);
362362
}
363363

364-
std::vector<std::uint8_t> fg::base64::neon_decode(std::string_view encoded) {
364+
fg::StaticVector<std::uint8_t> fg::base64::neon_decode(std::string_view encoded) {
365365
const auto encodedSize = encoded.size();
366366
const auto padding = getPadding(encoded);
367367

368-
std::vector<std::uint8_t> ret(getOutputSize(encodedSize, padding));
368+
StaticVector<std::uint8_t> ret(getOutputSize(encodedSize, padding));
369369
neon_decode_inplace(encoded, ret.data(), padding);
370370

371371
return ret;
@@ -425,11 +425,11 @@ void fg::base64::fallback_decode_inplace(std::string_view encoded, std::uint8_t*
425425
}
426426
}
427427

428-
std::vector<std::uint8_t> fg::base64::fallback_decode(std::string_view encoded) {
428+
fg::StaticVector<std::uint8_t> fg::base64::fallback_decode(std::string_view encoded) {
429429
const auto encodedSize = encoded.size();
430430
const auto padding = getPadding(encoded);
431431

432-
std::vector<std::uint8_t> ret(getOutputSize(encodedSize, padding));
432+
fg::StaticVector<std::uint8_t> ret(getOutputSize(encodedSize, padding));
433433
fallback_decode_inplace(encoded, ret.data(), padding);
434434

435435
return ret;
@@ -441,7 +441,7 @@ void fg::base64::decode_inplace(std::string_view encoded, std::uint8_t* output,
441441
return DecodeFunctionGetter::get()->inplace(encoded, output, padding);
442442
}
443443

444-
std::vector<std::uint8_t> fg::base64::decode(std::string_view encoded) {
444+
fg::StaticVector<std::uint8_t> fg::base64::decode(std::string_view encoded) {
445445
assert(encoded.size() % 4 == 0);
446446

447447
return DecodeFunctionGetter::get()->func(encoded);

src/fastgltf.cpp

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -626,18 +626,18 @@ fg::Expected<fg::DataSource> fg::Parser::decodeDataUri(URIView& uri) const noexc
626626
}
627627

628628
// Decode the base64 data into a traditional vector
629-
std::vector<std::uint8_t> uriData;
629+
auto padding = base64::getPadding(encodedData);
630+
fg::StaticVector<std::uint8_t> uriData(base64::getOutputSize(encodedData.size(), padding));
630631
if (config.decodeCallback != nullptr) {
631-
auto padding = base64::getPadding(encodedData);
632-
uriData.resize(base64::getOutputSize(encodedData.size(), padding));
633632
config.decodeCallback(encodedData, uriData.data(), padding, uriData.size(), config.userPointer);
634633
} else {
635-
uriData = base64::decode(encodedData);
634+
base64::decode_inplace(encodedData, uriData.data(), padding);
636635
}
637636

638-
sources::Vector source = {};
639-
source.mimeType = getMimeTypeFromString(mime);
640-
source.bytes = std::move(uriData);
637+
sources::Array source = {
638+
std::move(uriData),
639+
getMimeTypeFromString(mime),
640+
};
641641
return Expected<DataSource> { std::move(source) };
642642
}
643643

@@ -669,10 +669,12 @@ fg::Expected<fg::DataSource> fg::Parser::loadFileFromUri(URIView& uri) const noe
669669
}
670670
}
671671

672-
sources::Vector vectorSource = {};
673-
vectorSource.mimeType = MimeType::GltfBuffer;
674-
vectorSource.bytes.resize(length);
675-
file.read(reinterpret_cast<char*>(vectorSource.bytes.data()), length);
672+
StaticVector<std::uint8_t> data(length);
673+
file.read(reinterpret_cast<char*>(data.data()), length);
674+
sources::Array vectorSource = {
675+
std::move(data),
676+
MimeType::GltfBuffer,
677+
};
676678
return Expected<DataSource> { std::move(vectorSource) };
677679
}
678680

@@ -744,10 +746,9 @@ fg::Error fg::Parser::generateMeshIndices(fastgltf::Asset& asset) const {
744746
}
745747
auto& positionAccessor = asset.accessors[positionAttribute->second];
746748

747-
sources::Vector generatedIndices;
748-
generatedIndices.bytes.resize(positionAccessor.count * getElementByteSize(positionAccessor.type, positionAccessor.componentType));
749-
fastgltf::span<std::uint32_t> indices { reinterpret_cast<std::uint32_t*>(generatedIndices.bytes.data()),
750-
generatedIndices.bytes.size() / sizeof(std::uint32_t) };
749+
StaticVector<std::uint8_t> generatedIndices(positionAccessor.count * getElementByteSize(positionAccessor.type, positionAccessor.componentType));
750+
fastgltf::span<std::uint32_t> indices { reinterpret_cast<std::uint32_t*>(generatedIndices.data()),
751+
generatedIndices.size() / sizeof(std::uint32_t) };
751752
for (std::size_t i = 0; i < positionAccessor.count; ++i) {
752753
indices[i] = static_cast<std::uint32_t>(i);
753754
}
@@ -756,7 +757,7 @@ fg::Error fg::Parser::generateMeshIndices(fastgltf::Asset& asset) const {
756757

757758
auto bufferViewIdx = asset.bufferViews.size();
758759
auto& bufferView = asset.bufferViews.emplace_back();
759-
bufferView.byteLength = generatedIndices.bytes.size();
760+
bufferView.byteLength = generatedIndices.size_bytes();
760761
bufferView.bufferIndex = bufferIdx;
761762
bufferView.byteOffset = 0;
762763

@@ -769,9 +770,12 @@ fg::Error fg::Parser::generateMeshIndices(fastgltf::Asset& asset) const {
769770
accessor.normalized = false;
770771
accessor.bufferViewIndex = bufferViewIdx;
771772

773+
sources::Array indicesArray {
774+
std::move(generatedIndices),
775+
};
772776
auto& buffer = asset.buffers.emplace_back();
773-
buffer.byteLength = generatedIndices.bytes.size();
774-
buffer.data = std::move(generatedIndices);
777+
buffer.byteLength = generatedIndices.size_bytes();
778+
buffer.data = std::move(indicesArray);
775779
primitive.indicesAccessor = accessorIdx;
776780
}
777781
}
@@ -2022,7 +2026,7 @@ fg::Error fg::Parser::parseImages(simdjson::dom::array& images, Asset& asset) {
20222026
using T = std::decay_t<decltype(arg)>;
20232027

20242028
// This is kinda cursed
2025-
if constexpr (is_any<T, sources::CustomBuffer, sources::BufferView, sources::URI, sources::Vector>()) {
2029+
if constexpr (is_any<T, sources::CustomBuffer, sources::BufferView, sources::URI, sources::Array>()) {
20262030
arg.mimeType = getMimeTypeFromString(mimeType);
20272031
}
20282032
}, image.data);
@@ -3495,10 +3499,13 @@ fg::Expected<fg::Asset> fg::Parser::loadGltfBinary(GltfDataBuffer* buffer, fs::p
34953499
glbBuffer = sources::CustomBuffer { info.customId, MimeType::None };
34963500
}
34973501
} else {
3498-
sources::Vector vectorData = {};
3499-
vectorData.bytes.resize(binaryChunk.chunkLength);
3500-
read(vectorData.bytes.data(), binaryChunk.chunkLength);
3501-
vectorData.mimeType = MimeType::GltfBuffer;
3502+
StaticVector<std::uint8_t> binaryData(binaryChunk.chunkLength);
3503+
read(binaryData.data(), binaryChunk.chunkLength);
3504+
3505+
sources::Array vectorData = {
3506+
std::move(binaryData),
3507+
MimeType::GltfBuffer,
3508+
};
35023509
glbBuffer = std::move(vectorData);
35033510
}
35043511
} else {
@@ -3699,7 +3706,7 @@ void fg::Exporter::writeBuffers(const Asset& asset, std::string& json) {
36993706
// Covers BufferView and CustomBuffer.
37003707
errorCode = Error::InvalidGltf;
37013708
},
3702-
[&](const sources::Vector& vector) {
3709+
[&](const sources::Array& vector) {
37033710
if (bufferIdx == 0 && exportingBinary) {
37043711
bufferPaths.emplace_back(std::nullopt);
37053712
return;
@@ -3870,7 +3877,7 @@ void fg::Exporter::writeImages(const Asset& asset, std::string& json) {
38703877
json += std::string(R"("mimeType":")") + std::string(getMimeTypeString(bufferView.mimeType)) + '"';
38713878
imagePaths.emplace_back(std::nullopt);
38723879
},
3873-
[&](const sources::Vector& vector) {
3880+
[&](const sources::Array& vector) {
38743881
auto path = getImageFilePath(asset, imageIdx, vector.mimeType);
38753882
json += std::string(R"("uri":")") + fg::escapeString(path.string()) + '"';
38763883
imagePaths.emplace_back(path);
@@ -4745,7 +4752,7 @@ fg::Expected<fg::ExportResult<std::vector<std::byte>>> fg::Exporter::writeGltfBi
47454752
// TODO: Add ExportOption enumeration for disabling this?
47464753
const bool withEmbeddedBuffer = !asset.buffers.empty()
47474754
// We only support writing Vectors and ByteViews as embedded buffers
4748-
&& (std::holds_alternative<sources::Vector>(asset.buffers.front().data) || std::holds_alternative<sources::ByteView>(asset.buffers.front().data))
4755+
&& (std::holds_alternative<sources::Array>(asset.buffers.front().data) || std::holds_alternative<sources::ByteView>(asset.buffers.front().data))
47494756
&& asset.buffers.front().byteLength < std::numeric_limits<decltype(BinaryGltfChunk::chunkLength)>::max();
47504757

47514758
std::size_t binarySize = sizeof(BinaryGltfHeader) + sizeof(BinaryGltfChunk) + json.size();
@@ -4782,7 +4789,7 @@ fg::Expected<fg::ExportResult<std::vector<std::byte>>> fg::Exporter::writeGltfBi
47824789

47834790
std::visit(visitor {
47844791
[](auto arg) {},
4785-
[&](sources::Vector& vector) {
4792+
[&](sources::Array& vector) {
47864793
write(vector.bytes.data(), buffer.byteLength);
47874794
},
47884795
[&](sources::ByteView& byteView) {
@@ -4798,7 +4805,7 @@ namespace fastgltf {
47984805
void writeFile(const DataSource& dataSource, fs::path baseFolder, fs::path filePath) {
47994806
std::visit(visitor {
48004807
[](auto& arg) {},
4801-
[&](const fastgltf::sources::Vector &vector) {
4808+
[&](const fastgltf::sources::Array &vector) {
48024809
std::ofstream file(baseFolder / filePath, std::ios::out | std::ios::binary);
48034810
file.write(reinterpret_cast<const char *>(vector.bytes.data()),
48044811
static_cast<std::streamsize>(vector.bytes.size()));

tests/accessor_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ static const std::byte* getBufferData(const fastgltf::Buffer& buffer) {
1414

1515
std::visit(fastgltf::visitor {
1616
[](auto&) {},
17-
[&](const fastgltf::sources::Vector& vec) {
17+
[&](const fastgltf::sources::Array& vec) {
1818
result = reinterpret_cast<const std::byte*>(vec.bytes.data());
1919
},
2020
[&](const fastgltf::sources::ByteView& bv) {

0 commit comments

Comments
 (0)