add toggle for postprocessing and minor cleanup work
This commit is contained in:
@@ -84,8 +84,7 @@ EntityId AssetManager::loadGltf(const char *uri,
|
||||
ResourceBuffer rbuf = _resourceLoaderWrapper->load(uri);
|
||||
|
||||
// Parse the glTF file and create Filament entities.
|
||||
FilamentAsset *asset =
|
||||
_assetLoader->createAsset((uint8_t *)rbuf.data, rbuf.size);
|
||||
FilamentAsset *asset = _assetLoader->createAsset((uint8_t *)rbuf.data, rbuf.size);
|
||||
|
||||
if (!asset) {
|
||||
Log("Unable to parse asset");
|
||||
@@ -94,53 +93,64 @@ EntityId AssetManager::loadGltf(const char *uri,
|
||||
|
||||
const char *const *const resourceUris = asset->getResourceUris();
|
||||
const size_t resourceUriCount = asset->getResourceUriCount();
|
||||
|
||||
std::vector<ResourceBuffer> resourceBuffers;
|
||||
|
||||
for (size_t i = 0; i < resourceUriCount; i++) {
|
||||
string uri =
|
||||
string(relativeResourcePath) + string("/") + string(resourceUris[i]);
|
||||
string uri = string(relativeResourcePath) + string("/") + string(resourceUris[i]);
|
||||
Log("Loading resource URI from relative path %s", resourceUris[i], uri.c_str());
|
||||
ResourceBuffer buf = _resourceLoaderWrapper->load(uri.c_str());
|
||||
|
||||
resourceBuffers.push_back(buf);
|
||||
|
||||
ResourceLoader::BufferDescriptor b(buf.data, buf.size);
|
||||
_gltfResourceLoader->addResourceData(resourceUris[i], std::move(b));
|
||||
_resourceLoaderWrapper->free(buf);
|
||||
}
|
||||
|
||||
_gltfResourceLoader->loadResources(asset);
|
||||
// load resources synchronously
|
||||
if (!_gltfResourceLoader->loadResources(asset)) {
|
||||
Log("Unknown error loading glTF asset");
|
||||
_resourceLoaderWrapper->free(rbuf);
|
||||
for(auto& rb : resourceBuffers) {
|
||||
_resourceLoaderWrapper->free(rb);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
const utils::Entity *entities = asset->getEntities();
|
||||
RenderableManager &rm = _engine->getRenderableManager();
|
||||
for (int i = 0; i < asset->getEntityCount(); i++) {
|
||||
auto inst = rm.getInstance(entities[i]);
|
||||
rm.setCulling(inst, false);
|
||||
}
|
||||
|
||||
|
||||
_scene->addEntities(asset->getEntities(), asset->getEntityCount());
|
||||
|
||||
FilamentInstance* inst = asset->getInstance();
|
||||
inst->getAnimator()->updateBoneMatrices();
|
||||
inst->recomputeBoundingBoxes();
|
||||
|
||||
_scene->addEntities(asset->getEntities(), asset->getEntityCount());
|
||||
|
||||
asset->releaseSourceData();
|
||||
|
||||
Log("Load complete for GLTF at URI %s", uri);
|
||||
SceneAsset sceneAsset(asset);
|
||||
|
||||
|
||||
utils::Entity e = EntityManager::get().create();
|
||||
|
||||
EntityId eid = Entity::smuggle(e);
|
||||
|
||||
_entityIdLookup.emplace(eid, _assets.size());
|
||||
_assets.push_back(sceneAsset);
|
||||
|
||||
for(auto& rb : resourceBuffers) {
|
||||
_resourceLoaderWrapper->free(rb);
|
||||
}
|
||||
_resourceLoaderWrapper->free(rbuf);
|
||||
|
||||
Log("Finished loading glTF from %s", uri);
|
||||
|
||||
return eid;
|
||||
}
|
||||
|
||||
EntityId AssetManager::loadGlb(const char *uri, bool unlit) {
|
||||
|
||||
Log("Loading GLB at URI %s", uri);
|
||||
|
||||
|
||||
ResourceBuffer rbuf = _resourceLoaderWrapper->load(uri);
|
||||
|
||||
|
||||
Log("Loaded GLB of size %d at URI %s", rbuf.size, uri);
|
||||
|
||||
FilamentAsset *asset = _assetLoader->createAsset(
|
||||
(const uint8_t *)rbuf.data, rbuf.size);
|
||||
|
||||
@@ -153,8 +163,12 @@ EntityId AssetManager::loadGlb(const char *uri, bool unlit) {
|
||||
|
||||
_scene->addEntities(asset->getEntities(), entityCount);
|
||||
|
||||
_gltfResourceLoader->loadResources(asset);
|
||||
|
||||
if (!_gltfResourceLoader->loadResources(asset)) {
|
||||
Log("Unknown error loading glb asset");
|
||||
_resourceLoaderWrapper->free(rbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Entity *entities = asset->getEntities();
|
||||
|
||||
auto lights = asset->getLightEntities();
|
||||
@@ -220,12 +234,9 @@ bool AssetManager::reveal(EntityId entityId, const char* meshName) {
|
||||
void AssetManager::destroyAll() {
|
||||
for (auto& asset : _assets) {
|
||||
_scene->removeEntities(asset.mAsset->getEntities(),
|
||||
asset.mAsset->getEntityCount());
|
||||
|
||||
asset.mAsset->getEntityCount());
|
||||
_scene->removeEntities(asset.mAsset->getLightEntities(),
|
||||
asset.mAsset->getLightEntityCount());
|
||||
|
||||
_gltfResourceLoader->evictResourceData();
|
||||
asset.mAsset->getLightEntityCount());
|
||||
_assetLoader->destroyAsset(asset.mAsset);
|
||||
}
|
||||
_assets.clear();
|
||||
@@ -370,6 +381,10 @@ void AssetManager::remove(EntityId entityId) {
|
||||
return;
|
||||
}
|
||||
SceneAsset& sceneAsset = _assets[pos->second];
|
||||
|
||||
_assets.erase(std::remove_if(_assets.begin(), _assets.end(),
|
||||
[=](SceneAsset& asset) { return asset.mAsset == sceneAsset.mAsset; }),
|
||||
_assets.end());
|
||||
|
||||
_scene->removeEntities(sceneAsset.mAsset->getEntities(),
|
||||
sceneAsset.mAsset->getEntityCount());
|
||||
@@ -384,7 +399,8 @@ void AssetManager::remove(EntityId entityId) {
|
||||
}
|
||||
EntityManager& em = EntityManager::get();
|
||||
em.destroy(Entity::import(entityId));
|
||||
sceneAsset.mAsset = nullptr; // still need to remove sceneAsset somewhere...
|
||||
|
||||
|
||||
}
|
||||
|
||||
void AssetManager::setMorphTargetWeights(EntityId entityId, const char* const entityName, const float* const weights, const int count) {
|
||||
|
||||
@@ -143,7 +143,6 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
|
||||
|
||||
Log("Main camera created");
|
||||
_view = _engine->createView();
|
||||
_view->setPostProcessingEnabled(false);
|
||||
Log("View created");
|
||||
|
||||
setToneMapping(ToneMapping::ACES);
|
||||
@@ -247,6 +246,10 @@ FilamentViewer::FilamentViewer(const void* sharedContext, const ResourceLoaderWr
|
||||
_scene->addEntity(imageEntity);
|
||||
}
|
||||
|
||||
void FilamentViewer::setPostProcessing(bool enabled) {
|
||||
_view->setPostProcessingEnabled(enabled);
|
||||
}
|
||||
|
||||
void FilamentViewer::setBloom(float strength) {
|
||||
decltype(_view->getBloomOptions()) opts;
|
||||
opts.enabled = true;
|
||||
@@ -259,14 +262,20 @@ void FilamentViewer::setToneMapping(ToneMapping toneMapping) {
|
||||
ToneMapper* tm;
|
||||
switch(toneMapping) {
|
||||
case ToneMapping::ACES:
|
||||
Log("Setting tone mapping to ACES");
|
||||
tm = new ACESToneMapper();
|
||||
break;
|
||||
case ToneMapping::LINEAR:
|
||||
Log("Setting tone mapping to Linear");
|
||||
tm = new LinearToneMapper();
|
||||
break;
|
||||
case ToneMapping::FILMIC:
|
||||
Log("Setting tone mapping to Filmic");
|
||||
tm = new FilmicToneMapper();
|
||||
break;
|
||||
default:
|
||||
Log("ERROR: Unsupported tone mapping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +314,7 @@ void FilamentViewer::removeLight(EntityId entityId) {
|
||||
if(entity.isNull()) {
|
||||
Log("Error: light entity not found under ID %d", entityId);
|
||||
} else {
|
||||
auto removed = remove(_lights.begin(), _lights.end(), entity);
|
||||
remove(_lights.begin(), _lights.end(), entity);
|
||||
_scene->remove(entity);
|
||||
EntityManager::get().destroy(1, &entity);
|
||||
}
|
||||
@@ -347,11 +356,23 @@ void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) {
|
||||
ktxreader::Ktx1Bundle *bundle =
|
||||
new ktxreader::Ktx1Bundle(static_cast<const uint8_t *>(rb.data),
|
||||
static_cast<uint32_t>(rb.size));
|
||||
|
||||
|
||||
// because the ResourceBuffer will go out of scope before the texture callback is invoked, we need to make a copy to the heap
|
||||
ResourceBuffer* rbCopy = new ResourceBuffer(rb);
|
||||
|
||||
std::vector<void*>* callbackData = new std::vector<void*> { (void*)_resourceLoaderWrapper, rbCopy };
|
||||
|
||||
|
||||
_imageTexture =
|
||||
ktxreader::Ktx1Reader::createTexture(_engine, *bundle, false, [](void* userdata) {
|
||||
Ktx1Bundle* bundle = (Ktx1Bundle*) userdata;
|
||||
delete bundle;
|
||||
}, bundle);
|
||||
std::vector<void*>* vec = (std::vector<void*>*)userdata;
|
||||
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
|
||||
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
|
||||
loader->free(*rb);
|
||||
delete rb;
|
||||
delete vec;
|
||||
}, callbackData);
|
||||
|
||||
auto info = bundle->getInfo();
|
||||
_imageWidth = info.pixelWidth;
|
||||
@@ -396,6 +417,9 @@ void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) {
|
||||
Texture::Type::FLOAT, nullptr, freeCallback, image);
|
||||
|
||||
_imageTexture->setImage(*_engine, 0, std::move(pbd));
|
||||
// we don't need to free the ResourceBuffer in the texture callback because LinearImage takes a copy
|
||||
// (check if this is correct ? )
|
||||
_resourceLoaderWrapper->free(rb);
|
||||
}
|
||||
|
||||
void FilamentViewer::loadTextureFromPath(string path) {
|
||||
@@ -417,13 +441,11 @@ void FilamentViewer::loadTextureFromPath(string path) {
|
||||
} else if(endsWith(path, pngExt)) {
|
||||
loadPngTexture(path, rb);
|
||||
}
|
||||
_resourceLoaderWrapper->free(rb);
|
||||
}
|
||||
|
||||
void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) {
|
||||
_imageMaterial->setDefaultParameter("showImage", 0);
|
||||
_imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, float4(r, g, b, a));
|
||||
const Viewport& vp = _view->getViewport();
|
||||
_imageMaterial->setDefaultParameter("transform", _imageScale);
|
||||
}
|
||||
|
||||
@@ -685,11 +707,12 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) {
|
||||
|
||||
auto asset = _assetManager->getAssetByEntityId(entityId);
|
||||
if(!asset) {
|
||||
Log("Failed to find asset attached to specified entity id.");
|
||||
Log("Failed to find asset under entity id %d.", entityId);
|
||||
return false;
|
||||
}
|
||||
size_t count = asset->getCameraEntityCount();
|
||||
if (count == 0) {
|
||||
Log("Failed, no cameras found in current asset.");
|
||||
Log("No cameras found attached to specified entity.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -701,7 +724,7 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) {
|
||||
auto inst = _ncm->getInstance(cameras[0]);
|
||||
const char *name = _ncm->getName(inst);
|
||||
target = cameras[0];
|
||||
Log("No camera specified, using first : %s", name);
|
||||
Log("No camera specified, using first camera node found (%s)", name);
|
||||
} else {
|
||||
for (int j = 0; j < count; j++) {
|
||||
auto inst = _ncm->getInstance(cameras[j]);
|
||||
@@ -720,61 +743,76 @@ bool FilamentViewer::setCamera(EntityId entityId, const char *cameraName) {
|
||||
Camera *camera = _engine->getCameraComponent(target);
|
||||
if(!camera) {
|
||||
Log("Failed to retrieve camera component for target");
|
||||
return false;
|
||||
}
|
||||
_view->setCamera(camera);
|
||||
|
||||
const Viewport &vp = _view->getViewport();
|
||||
const double aspect = (double)vp.width / vp.height;
|
||||
|
||||
// const float aperture = camera->getAperture();
|
||||
// const float shutterSpeed = camera->getShutterSpeed();
|
||||
// const float sens = camera->getSensitivity();
|
||||
// camera->setExposure(1.0f);
|
||||
|
||||
camera->setScaling({1.0 / aspect, 1.0});
|
||||
|
||||
Log("Successfully set view camera to target");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilamentViewer::loadSkybox(const char *const skyboxPath) {
|
||||
Log("Loading skybox from %s", skyboxPath);
|
||||
|
||||
removeSkybox();
|
||||
|
||||
if (skyboxPath) {
|
||||
if (!skyboxPath) {
|
||||
Log("No skybox path provided, removed skybox.");
|
||||
}
|
||||
|
||||
Log("Loading skybox from path %s", skyboxPath);
|
||||
|
||||
ResourceBuffer skyboxBuffer = _resourceLoaderWrapper->load(skyboxPath);
|
||||
|
||||
// because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap
|
||||
ResourceBuffer* skyboxBufferCopy = new ResourceBuffer(skyboxBuffer);
|
||||
|
||||
if(skyboxBuffer.size <= 0) {
|
||||
Log("Could not load skybox resource.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Log("Loaded skybox data of length %d", skyboxBuffer.size);
|
||||
|
||||
std::vector<void*>* callbackData = new std::vector<void*> { (void*)_resourceLoaderWrapper, skyboxBufferCopy };
|
||||
|
||||
image::Ktx1Bundle *skyboxBundle =
|
||||
new image::Ktx1Bundle(static_cast<const uint8_t *>(skyboxBuffer.data),
|
||||
static_cast<uint32_t>(skyboxBuffer.size));
|
||||
|
||||
_skyboxTexture =
|
||||
ktxreader::Ktx1Reader::createTexture(_engine, *skyboxBundle, false, [](void* userdata) {
|
||||
image::Ktx1Bundle* bundle = (image::Ktx1Bundle*) userdata;
|
||||
delete bundle;
|
||||
}, skyboxBundle);
|
||||
std::vector<void*>* vec = (std::vector<void*>*)userdata;
|
||||
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
|
||||
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
|
||||
loader->free(*rb);
|
||||
delete rb;
|
||||
delete vec;
|
||||
Log("Skybox load complete.");
|
||||
}, callbackData);
|
||||
_skybox =
|
||||
filament::Skybox::Builder().environment(_skyboxTexture).build(*_engine);
|
||||
|
||||
_scene->setSkybox(_skybox);
|
||||
_resourceLoaderWrapper->free(skyboxBuffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FilamentViewer::removeSkybox() {
|
||||
Log("Removing skybox");
|
||||
_scene->setSkybox(nullptr);
|
||||
if(_skybox) {
|
||||
|
||||
_engine->destroy(_skybox);
|
||||
_engine->destroy(_skyboxTexture);
|
||||
_skybox = nullptr;
|
||||
}
|
||||
if(_skyboxTexture) {
|
||||
_engine->destroy(_skyboxTexture);
|
||||
_skyboxTexture = nullptr;
|
||||
}
|
||||
_scene->setSkybox(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentViewer::removeIbl() {
|
||||
@@ -794,6 +832,8 @@ void FilamentViewer::loadIbl(const char *const iblPath, float intensity) {
|
||||
|
||||
// Load IBL.
|
||||
ResourceBuffer iblBuffer = _resourceLoaderWrapper->load(iblPath);
|
||||
// because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap
|
||||
ResourceBuffer* iblBufferCopy = new ResourceBuffer(iblBuffer);
|
||||
|
||||
if(iblBuffer.size == 0) {
|
||||
Log("Error loading IBL, resource could not be loaded.");
|
||||
@@ -805,11 +845,18 @@ void FilamentViewer::loadIbl(const char *const iblPath, float intensity) {
|
||||
static_cast<uint32_t>(iblBuffer.size));
|
||||
math::float3 harmonics[9];
|
||||
iblBundle->getSphericalHarmonics(harmonics);
|
||||
_iblTexture =
|
||||
|
||||
std::vector<void*>* callbackData = new std::vector<void*> { (void*)_resourceLoaderWrapper, iblBufferCopy };
|
||||
|
||||
_iblTexture =
|
||||
ktxreader::Ktx1Reader::createTexture(_engine, *iblBundle, false, [](void* userdata) {
|
||||
image::Ktx1Bundle* bundle = (image::Ktx1Bundle*) userdata;
|
||||
delete bundle;
|
||||
}, iblBundle);
|
||||
std::vector<void*>* vec = (std::vector<void*>*)userdata;
|
||||
ResourceLoaderWrapper* loader = (ResourceLoaderWrapper*)vec->at(0);
|
||||
ResourceBuffer* rb = (ResourceBuffer*) vec->at(1);
|
||||
loader->free(*rb);
|
||||
delete rb;
|
||||
delete vec;
|
||||
}, callbackData);
|
||||
_indirectLight = IndirectLight::Builder()
|
||||
.reflections(_iblTexture)
|
||||
.irradiance(3, harmonics)
|
||||
@@ -817,9 +864,7 @@ void FilamentViewer::loadIbl(const char *const iblPath, float intensity) {
|
||||
.build(*_engine);
|
||||
_scene->setIndirectLight(_indirectLight);
|
||||
|
||||
_resourceLoaderWrapper->free(iblBuffer);
|
||||
|
||||
Log("Skybox/IBL load complete.");
|
||||
Log("IBL loaded.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ extern "C" {
|
||||
);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_bone_animation(
|
||||
FLUTTER_PLUGIN_EXPORT void set_bone_animation(
|
||||
void* assetManager,
|
||||
EntityId asset,
|
||||
const float* const frameData,
|
||||
@@ -254,6 +254,10 @@ extern "C" {
|
||||
);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_post_processing(void* const viewer, bool enabled) {
|
||||
((FilamentViewer*)viewer)->setPostProcessing(enabled);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// void set_bone_transform(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
|
||||
extern "C" {
|
||||
#include "PolyvoxFilamentFFIApi.h"
|
||||
}
|
||||
#include "PolyvoxFilamentFFIApi.h"
|
||||
|
||||
#include "FilamentViewer.hpp"
|
||||
#include "filament/LightManager.h"
|
||||
@@ -190,6 +188,15 @@ extern "C"
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT EntityId load_gltf_ffi(void* const assetManager, const char *path, const char *relativeResourcePath)
|
||||
{
|
||||
std::packaged_task<EntityId()> lambda([&]() mutable
|
||||
{ return load_gltf(assetManager, path, relativeResourcePath); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT EntityId load_glb_ffi(void* const assetManager, const char *path, bool unlit)
|
||||
{
|
||||
std::packaged_task<EntityId()> lambda([&]() mutable
|
||||
@@ -362,28 +369,31 @@ extern "C"
|
||||
// TODO
|
||||
}
|
||||
|
||||
void play_animation_ffi(void* const assetManager, EntityId asset, int index, bool loop, bool reverse, bool replaceActive, float crossfade)
|
||||
FLUTTER_PLUGIN_EXPORT void play_animation_ffi(void* const assetManager, EntityId asset, int index, bool loop, bool reverse, bool replaceActive, float crossfade)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ play_animation(assetManager, asset, index, loop, reverse, replaceActive, crossfade); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
void set_animation_frame_ffi(void* const assetManager, EntityId asset, int animationIndex, int animationFrame)
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_animation_frame_ffi(void* const assetManager, EntityId asset, int animationIndex, int animationFrame)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ set_animation_frame(assetManager, asset, animationIndex, animationFrame); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
void stop_animation_ffi(void* const assetManager, EntityId asset, int index)
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void stop_animation_ffi(void* const assetManager, EntityId asset, int index)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ stop_animation(assetManager, asset, index); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
int get_animation_count_ffi(void* const assetManager, EntityId asset)
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT int get_animation_count_ffi(void* const assetManager, EntityId asset)
|
||||
{
|
||||
std::packaged_task<int()> lambda([&]
|
||||
{ return get_animation_count(assetManager, asset); });
|
||||
@@ -391,7 +401,7 @@ extern "C"
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
void get_animation_name_ffi(void* const assetManager, EntityId asset, char *const outPtr, int index)
|
||||
FLUTTER_PLUGIN_EXPORT void get_animation_name_ffi(void* const assetManager, EntityId asset, char *const outPtr, int index)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
get_animation_name(assetManager, asset, outPtr, index);
|
||||
@@ -400,6 +410,14 @@ extern "C"
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_post_processing_ffi(void* const viewer, bool enabled) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
set_post_processing(viewer, enabled);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi() {
|
||||
Log("Dummy called");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user