Skip to content

Commit

Permalink
Add: Load external files from an APK
Browse files Browse the repository at this point in the history
  • Loading branch information
DethRaid authored and spnda committed Mar 6, 2024
1 parent a933ac6 commit 5253adb
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 9 deletions.
13 changes: 9 additions & 4 deletions include/fastgltf/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -674,15 +674,17 @@ namespace fastgltf {
};

#if defined(__ANDROID__)
class AndroidGltfDataBuffer : public GltfDataBuffer {
AAssetManager* assetManager;
void setAndroidAssetManager(AAssetManager* assetManager) noexcept;

class AndroidGltfDataBuffer : public GltfDataBuffer {
public:
explicit AndroidGltfDataBuffer(AAssetManager* assetManager) noexcept;
explicit AndroidGltfDataBuffer() noexcept;
~AndroidGltfDataBuffer() noexcept = default;

/**
* Loads a file from within an Android APK
* Loads a file from within an Android APK.
*
* @note This requires a valid AAssetManager to have been specified through fastgltf::setAndroidAssetManager.
*/
bool loadFromAndroidAsset(const std::filesystem::path& path, std::uint64_t byteOffset = 0) noexcept;
};
Expand Down Expand Up @@ -732,6 +734,9 @@ namespace fastgltf {

[[nodiscard]] auto decodeDataUri(URIView& uri) const noexcept -> Expected<DataSource>;
[[nodiscard]] auto loadFileFromUri(URIView& uri) const noexcept -> Expected<DataSource>;
#if defined(__ANDROID__)
[[nodiscard]] auto loadFileFromApk(const std::filesystem::path& filepath) const noexcept -> Expected<DataSource>;
#endif

Error generateMeshIndices(Asset& asset) const;

Expand Down
1 change: 1 addition & 0 deletions include/fastgltf/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,7 @@ namespace fastgltf {
static void decodePercents(std::string& x) noexcept;

[[nodiscard]] auto string() const noexcept -> std::string_view;
[[nodiscard]] auto c_str() const noexcept -> const char*;

[[nodiscard]] auto scheme() const noexcept -> std::string_view;
[[nodiscard]] auto userinfo() const noexcept -> std::string_view;
Expand Down
11 changes: 11 additions & 0 deletions include/fastgltf/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,17 @@ namespace fastgltf {
? static_cast<unsigned_t>(-(val + 1)) + 1
: static_cast<unsigned_t>(val);
}

template <auto callback>
struct UniqueDeleter {
template <typename T>
constexpr void operator()(T* t) const {
callback(t);
}
};

template <typename T, auto callback>
using deletable_unique_ptr = std::unique_ptr<T, UniqueDeleter<callback>>;
} // namespace fastgltf

#ifdef _MSC_VER
Expand Down
71 changes: 66 additions & 5 deletions src/fastgltf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ namespace fg = fastgltf;
namespace fs = std::filesystem;

namespace fastgltf {
#if defined(__ANDROID__)
/**
* Global asset manager that can be accessed freely.
* The value of this global should only be set by fastgltf::setAndroidAssetManager.
*/
static AAssetManager* androidAssetManager = nullptr;
#endif

constexpr std::uint32_t binaryGltfHeaderMagic = 0x46546C67; // ASCII for "glTF".
constexpr std::uint32_t binaryGltfJsonChunkMagic = 0x4E4F534A;
constexpr std::uint32_t binaryGltfDataChunkMagic = 0x004E4942;
Expand Down Expand Up @@ -629,6 +637,7 @@ void fg::URI::decodePercents(std::string& x) noexcept {
}

std::string_view fg::URI::string() const noexcept { return uri; }
const char* fg::URI::c_str() const noexcept { return uri.c_str(); }
std::string_view fg::URI::scheme() const noexcept { return view.scheme(); }
std::string_view fg::URI::userinfo() const noexcept { return view.userinfo(); }
std::string_view fg::URI::host() const noexcept { return view.host(); }
Expand Down Expand Up @@ -706,6 +715,42 @@ fg::Expected<fg::DataSource> fg::Parser::decodeDataUri(URIView& uri) const noexc
return Expected<DataSource> { std::move(source) };
}

#if defined(__ANDROID__)
fg::Expected<fg::DataSource> fg::Parser::loadFileFromApk(const fs::path& path) const noexcept {
auto file = deletable_unique_ptr<AAsset, AAsset_close>(
AAssetManager_open(androidAssetManager, path.c_str(), AASSET_MODE_BUFFER));
if (file == nullptr) {
return Expected<DataSource>(Error::MissingExternalBuffer);
}

const auto length = AAsset_getLength(file.get());
if (length == 0) {
return Expected<DataSource>(Error::MissingExternalBuffer);
}

if (config.mapCallback != nullptr) {
auto info = config.mapCallback(static_cast<std::uint64_t>(length), config.userPointer);
if (info.mappedMemory != nullptr) {
const sources::CustomBuffer customBufferSource = { info.customId, MimeType::None };
AAsset_read(file.get(), info.mappedMemory, length);
if (config.unmapCallback != nullptr) {
config.unmapCallback(&info, config.userPointer);
}

return Expected<DataSource> { customBufferSource };
}
}

sources::Array arraySource = {
StaticVector<std::uint8_t>(length),
MimeType::GltfBuffer
};
AAsset_read(file.get(), arraySource.bytes.data(), length);

return Expected<DataSource> { std::move(arraySource) };
}
#endif

fg::Expected<fg::DataSource> fg::Parser::loadFileFromUri(URIView& uri) const noexcept {
URI decodedUri(uri.path()); // Re-allocate so we can decode potential characters.
#if FASTGLTF_CPP_20
Expand All @@ -717,8 +762,18 @@ fg::Expected<fg::DataSource> fg::Parser::loadFileFromUri(URIView& uri) const noe
#else
auto path = directory / fs::u8path(decodedUri.path());
#endif
std::error_code error;

#if defined(__ANDROID__)
if (androidAssetManager != nullptr) {
// Try to load external buffers from the APK. If they're not there, fall through to the file case
if (auto androidResult = loadFileFromApk(path); androidResult.error() == Error::None) {
return Expected<DataSource>(std::move(androidResult.get()));
}
}
#endif

// If we were instructed to load external buffers and the files don't exist, we'll return an error.
std::error_code error;
if (!fs::exists(path, error) || error) {
return Expected<DataSource> { Error::MissingExternalBuffer };
}
Expand Down Expand Up @@ -3779,19 +3834,23 @@ bool fg::GltfDataBuffer::loadFromFile(const fs::path& path, std::uint64_t byteOf

#pragma region AndroidGltfDataBuffer
#if defined(__ANDROID__)
fg::AndroidGltfDataBuffer::AndroidGltfDataBuffer(AAssetManager* assetManager) noexcept : assetManager{assetManager} {}
void fg::setAndroidAssetManager(AAssetManager* assetManager) noexcept {
androidAssetManager = assetManager;
}

fg::AndroidGltfDataBuffer::AndroidGltfDataBuffer() noexcept = default;

bool fg::AndroidGltfDataBuffer::loadFromAndroidAsset(const fs::path& path, std::uint64_t byteOffset) noexcept {
if (assetManager == nullptr) {
if (androidAssetManager == nullptr) {
return false;
}

using namespace simdjson;

const auto filenameString = path.string();

auto assetDeleter = [](AAsset* file) { AAsset_close(file); };
auto file = std::unique_ptr<AAsset, decltype(assetDeleter)>(AAssetManager_open(assetManager, filenameString.c_str(), AASSET_MODE_BUFFER), assetDeleter);
auto file = deletable_unique_ptr<AAsset, AAsset_close>(
AAssetManager_open(androidAssetManager, filenameString.c_str(), AASSET_MODE_BUFFER));
if (file == nullptr) {
return false;
}
Expand Down Expand Up @@ -3885,10 +3944,12 @@ fg::Expected<fg::Asset> fg::Parser::loadGltf(GltfDataBuffer* buffer, fs::path di
fg::Expected<fg::Asset> fg::Parser::loadGltfJson(GltfDataBuffer* buffer, fs::path directory, Options options, Category categories) {
using namespace simdjson;

#if !defined(__ANDROID__)
// If we never have to load the files ourselves, we're fine with the directory being invalid/blank.
if (std::error_code ec; hasBit(options, Options::LoadExternalBuffers) && (!fs::is_directory(directory, ec) || ec)) {
return Expected<Asset>(Error::InvalidPath);
}
#endif

this->options = options;
this->directory = std::move(directory);
Expand Down

0 comments on commit 5253adb

Please sign in to comment.