diff --git a/include/fastgltf/core.hpp b/include/fastgltf/core.hpp index f2035bf07..49919af13 100644 --- a/include/fastgltf/core.hpp +++ b/include/fastgltf/core.hpp @@ -621,6 +621,7 @@ namespace fastgltf { }; class GltfDataBuffer : public GltfDataGetter { + protected: std::unique_ptr buffer; std::size_t allocatedSize = 0; @@ -687,6 +688,51 @@ namespace fastgltf { } }; +#if defined(__APPLE__) || defined(__linux__) + /** Maps a glTF file into memory using mmap */ + class MappedGltfFile : public GltfDataGetter { + void* mappedFile = nullptr; + std::uint64_t fileSize = 0; + + std::size_t idx = 0; + + Error error = Error::None; + + explicit MappedGltfFile(const std::filesystem::path& path) noexcept; + + public: + explicit MappedGltfFile() = default; + MappedGltfFile(const MappedGltfFile& other) = delete; + MappedGltfFile& operator=(const MappedGltfFile& other) = delete; + MappedGltfFile(MappedGltfFile&& other) noexcept; + MappedGltfFile& operator=(MappedGltfFile&& other) noexcept; + + static Expected FromPath(const std::filesystem::path& path) noexcept { + MappedGltfFile buffer(path); + if (buffer.error != fastgltf::Error::None) { + return buffer.error; + } + return std::move(buffer); + } + + ~MappedGltfFile() noexcept; + + void read(void* ptr, std::size_t count) override; + + [[nodiscard]] span read(std::size_t count, std::size_t padding) override; + + void reset() override; + + [[nodiscard]] std::size_t bytesRead() override; + + [[nodiscard]] std::size_t totalSize() override; + + [[nodiscard]] explicit operator span() { + return span(static_cast(mappedFile), fileSize); + } + }; +#endif + class GltfFileStream : public GltfDataGetter { std::ifstream fileStream; std::vector buf; diff --git a/src/fastgltf.cpp b/src/fastgltf.cpp index 541fc5597..6b987840a 100644 --- a/src/fastgltf.cpp +++ b/src/fastgltf.cpp @@ -39,6 +39,13 @@ #include #endif +#if defined(__APPLE__) || defined(__linux__) +#include +#include +#include +#include +#endif + #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 5030) // attribute 'x' is not recognized @@ -3808,8 +3815,89 @@ std::size_t fg::GltfDataBuffer::totalSize() { return dataSize; } -fg::GltfFileStream::GltfFileStream(const std::filesystem::path &path) : fileStream(path, std::ios::binary) { - fileSize = std::filesystem::file_size(path); +#if defined(__APPLE__) || defined(__linux__) +fg::MappedGltfFile::MappedGltfFile(const fs::path& path) noexcept { + // Open the file + int fd = open(path.c_str(), O_RDONLY, 0); + if (fd == 0) { + // TODO: Cover actual error messages using std::strerror(errno)? + error = Error::InvalidPath; + return; + } + + // Get the file size + struct stat statInfo; + if (fstat(fd, &statInfo) != 0) { + error = Error::InvalidPath; + return; + } + + // Map the file + mappedFile = mmap(nullptr, + statInfo.st_size, + PROT_READ, + MAP_PRIVATE, + fd, + 0); + if (mappedFile != MAP_FAILED) { + fileSize = static_cast(statInfo.st_size); + } else { + std::perror("Mapping file"); + error = Error::FileBufferAllocationFailed; + } + + // The file descriptor is not used for the memory mapped and is not required anymore. + close(fd); +} + +fg::MappedGltfFile::MappedGltfFile(fastgltf::MappedGltfFile &&other) noexcept { + // Make sure that munmap is never called when other gets destructud. + mappedFile = std::exchange(other.mappedFile, MAP_FAILED); + fileSize = other.fileSize; + idx = other.idx; + error = other.error; +} + +fg::MappedGltfFile& fg::MappedGltfFile::operator=(fastgltf::MappedGltfFile &&other) noexcept { + mappedFile = std::exchange(other.mappedFile, MAP_FAILED); + fileSize = other.fileSize; + idx = other.idx; + error = other.error; + return *this; +} + +fg::MappedGltfFile::~MappedGltfFile() noexcept { + if (mappedFile != MAP_FAILED) { + munmap(mappedFile, fileSize); + } +} + +void fg::MappedGltfFile::read(void *ptr, std::size_t count) { + std::memcpy(ptr, static_cast(mappedFile) + idx, count); + idx += count; +} + +fg::span fg::MappedGltfFile::read(std::size_t count, std::size_t padding) { + span sub(static_cast(mappedFile) + idx, count); + idx += count; + return sub; +} + +void fg::MappedGltfFile::reset() { + idx = 0; +} + +std::size_t fg::MappedGltfFile::bytesRead() { + return idx; +} + +std::size_t fg::MappedGltfFile::totalSize() { + return fileSize; +} +#endif + +fg::GltfFileStream::GltfFileStream(const fs::path& path) : fileStream(path, std::ios::binary) { + fileSize = fs::file_size(path); } bool fg::GltfFileStream::isOpen() const { diff --git a/tests/basic_test.cpp b/tests/basic_test.cpp index f1e328c7a..8ea49e7bc 100644 --- a/tests/basic_test.cpp +++ b/tests/basic_test.cpp @@ -666,3 +666,19 @@ TEST_CASE("Test extras callback", "[gltf-loader]") { REQUIRE(nodeNames[2] == "Shoe.obj"); } } + +#if defined(__APPLE__) || defined(__linux__) +TEST_CASE("Test glTF file loading", "[gltf-loader]") { + SECTION("Mapped files") { + auto cubePath = sampleModels / "2.0" / "Cube" / "glTF"; + auto mappedFile = fastgltf::MappedGltfFile::FromPath(cubePath / "Cube.gltf"); + REQUIRE(mappedFile.error() == fastgltf::Error::None); + + REQUIRE(fastgltf::determineGltfFileType(mappedFile.get()) == fastgltf::GltfType::glTF); + + fastgltf::Parser parser; + auto asset = parser.loadGltfJson(mappedFile.get(), cubePath); + REQUIRE(asset.error() == fastgltf::Error::None); + } +} +#endif