From fcad31806f91aba20bc47cc04d28981ed85c02e7 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Fri, 22 Sep 2023 08:17:50 +0800 Subject: [PATCH] manual OpenGL texture instantiation on Android --- android/src/main/cpp/FilamentAndroid.cpp | 26 + android/src/main/cpp/JobSystem.cpp | 610 --------- android/src/main/cpp/StbProvider.cpp | 261 ---- android/src/main/cpp/filament_android.cpp | 90 -- .../google/android/filament/SwapChain.java | 144 --- .../filament/android/DisplayHelper.java | 202 --- .../android/filament/android/UiHelper.java | 581 --------- .../app/polyvox/filament/FilamentInterop.kt | 187 ++- .../polyvox/filament/PolyvoxFilamentPlugin.kt | 1142 ++++++++++------- 9 files changed, 798 insertions(+), 2445 deletions(-) create mode 100644 android/src/main/cpp/FilamentAndroid.cpp delete mode 100644 android/src/main/cpp/JobSystem.cpp delete mode 100644 android/src/main/cpp/StbProvider.cpp delete mode 100644 android/src/main/cpp/filament_android.cpp delete mode 100644 android/src/main/java/com/google/android/filament/SwapChain.java delete mode 100644 android/src/main/java/com/google/android/filament/android/DisplayHelper.java delete mode 100644 android/src/main/java/com/google/android/filament/android/UiHelper.java diff --git a/android/src/main/cpp/FilamentAndroid.cpp b/android/src/main/cpp/FilamentAndroid.cpp new file mode 100644 index 00000000..7353f04b --- /dev/null +++ b/android/src/main/cpp/FilamentAndroid.cpp @@ -0,0 +1,26 @@ +#include +#include + +extern "C" { + + #include "PolyvoxFilamentApi.h" + + const void* create_filament_viewer_android( + jobject surface, JNIEnv* env, ResourceLoaderWrapper* loaderWrapper + ) { + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + return create_filament_viewer(window,loaderWrapper); + } + + void create_swap_chain_android( + const void* const viewer, + jobject surface, + JNIEnv* env, + uint32_t width, + uint32_t height + ) { + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + create_swap_chain(viewer, window, width, height); + } + +} diff --git a/android/src/main/cpp/JobSystem.cpp b/android/src/main/cpp/JobSystem.cpp deleted file mode 100644 index 7ce947ed..00000000 --- a/android/src/main/cpp/JobSystem.cpp +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Note: The overhead of SYSTRACE_TAG_JOBSYSTEM is not negligible especially with parallel_for(). -#ifndef SYSTRACE_TAG -//#define SYSTRACE_TAG SYSTRACE_TAG_JOBSYSTEM -#define SYSTRACE_TAG SYSTRACE_TAG_NEVER -#endif - -// when SYSTRACE_TAG_JOBSYSTEM is used, enables even heavier systraces -#define HEAVY_SYSTRACE 0 - -// enable for catching hangs waiting on a job to finish -static constexpr bool DEBUG_FINISH_HANGS = false; - -#include - -#include -#include -#include -#include - -#include - -#include - -#if !defined(WIN32) -# include -#endif - -#ifdef __ANDROID__ -# include -# include -# ifndef ANDROID_PRIORITY_URGENT_DISPLAY -# define ANDROID_PRIORITY_URGENT_DISPLAY -8 // see include/system/thread_defs.h -# endif -# ifndef ANDROID_PRIORITY_DISPLAY -# define ANDROID_PRIORITY_DISPLAY -4 // see include/system/thread_defs.h -# endif -# ifndef ANDROID_PRIORITY_NORMAL -# define ANDROID_PRIORITY_NORMAL 0 // see include/system/thread_defs.h -# endif -#elif defined(__linux__) -// There is no glibc wrapper for gettid on linux so we need to syscall it. -# include -# include -# define gettid() syscall(SYS_gettid) -#endif - -#if HEAVY_SYSTRACE -# define HEAVY_SYSTRACE_CALL() SYSTRACE_CALL() -# define HEAVY_SYSTRACE_NAME(name) SYSTRACE_NAME(name) -# define HEAVY_SYSTRACE_VALUE32(name, v) SYSTRACE_VALUE32(name, v) -#else -# define HEAVY_SYSTRACE_CALL() -# define HEAVY_SYSTRACE_NAME(name) -# define HEAVY_SYSTRACE_VALUE32(name, v) -#endif - -namespace utils { - -void JobSystem::setThreadName(const char* name) noexcept { -#if defined(__linux__) - pthread_setname_np(pthread_self(), name); -#elif defined(__APPLE__) - pthread_setname_np(name); -#else -// TODO: implement setting thread name on WIN32 -#endif -} - -void JobSystem::setThreadPriority(Priority priority) noexcept { -#ifdef __ANDROID__ - int androidPriority = 0; - switch (priority) { - case Priority::NORMAL: - androidPriority = ANDROID_PRIORITY_NORMAL; - break; - case Priority::DISPLAY: - androidPriority = ANDROID_PRIORITY_DISPLAY; - break; - case Priority::URGENT_DISPLAY: - androidPriority = ANDROID_PRIORITY_URGENT_DISPLAY; - break; - } - setpriority(PRIO_PROCESS, 0, androidPriority); -#endif -} - -void JobSystem::setThreadAffinityById(size_t id) noexcept { -#if defined(__linux__) - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(id, &set); - sched_setaffinity(gettid(), sizeof(set), &set); -#endif -} - -JobSystem::JobSystem(const size_t userThreadCount, const size_t adoptableThreadsCount) noexcept - : mJobPool("JobSystem Job pool", MAX_JOB_COUNT * sizeof(Job)), - mJobStorageBase(static_cast(mJobPool.getAllocator().getCurrent())) -{ - SYSTRACE_ENABLE(); - - int threadPoolCount = userThreadCount; - if (threadPoolCount == 0) { - // default value, system dependant - int hwThreads = std::thread::hardware_concurrency(); - if (UTILS_HAS_HYPER_THREADING) { - // For now we avoid using HT, this simplifies profiling. - // TODO: figure-out what to do with Hyper-threading - // since we assumed HT, always round-up to an even number of cores (to play it safe) - hwThreads = (hwThreads + 1) / 2; - } - // one of the thread will be the user thread - threadPoolCount = hwThreads - 1; - } - // make sure we have at least one thread in the thread pool - threadPoolCount = std::max(1, threadPoolCount); - // and also limit the pool to 32 threads - threadPoolCount = std::min(UTILS_HAS_THREADING ? 32 : 0, threadPoolCount); - - mThreadStates = aligned_vector(threadPoolCount + adoptableThreadsCount); - mThreadCount = uint16_t(threadPoolCount); - mParallelSplitCount = (uint8_t)std::ceil((std::log2f(threadPoolCount + adoptableThreadsCount))); - - static_assert(std::atomic::is_always_lock_free); - static_assert(std::atomic::is_always_lock_free); - - std::random_device rd; - const size_t hardwareThreadCount = mThreadCount; - auto& states = mThreadStates; - - #pragma nounroll - for (size_t i = 0, n = states.size(); i < n; i++) { - auto& state = states[i]; - state.rndGen = default_random_engine(rd()); - state.id = (uint32_t)i; - state.js = this; - if (i < hardwareThreadCount) { - // don't start a thread of adoptable thread slots - state.thread = std::thread(&JobSystem::loop, this, &state); - } - } -} - -JobSystem::~JobSystem() { - requestExit(); - - #pragma nounroll - for (auto &state : mThreadStates) { - // adopted threads are not joinable - if (state.thread.joinable()) { - state.thread.join(); - } - } -} - -inline void JobSystem::incRef(Job const* job) noexcept { - // no action is taken when incrementing the reference counter, therefore we can safely use - // memory_order_relaxed. - job->refCount.fetch_add(1, std::memory_order_relaxed); -} - -UTILS_NOINLINE -void JobSystem::decRef(Job const* job) noexcept { - - // We must ensure that accesses from other threads happen before deleting the Job. - // To accomplish this, we need to guarantee that no read/writes are reordered after the - // dec-ref, because ANOTHER thread could hold the last reference (after us) and that thread - // needs to see all accesses completed before it deletes the object. This is done - // with memory_order_release. - // Similarly, we need to guarantee that no read/write are reordered before the last decref, - // or some other thread could see a destroyed object before the ref-count is 0. This is done - // with memory_order_acquire. - auto c = job->refCount.fetch_sub(1, std::memory_order_acq_rel); - assert(c > 0); - if (c == 1) { - // This was the last reference, it's safe to destroy the job. - mJobPool.destroy(job); - } -} - -void JobSystem::requestExit() noexcept { - mExitRequested.store(true); - std::lock_guard lock(mWaiterLock); - mWaiterCondition.notify_all(); -} - -inline bool JobSystem::exitRequested() const noexcept { - // memory_order_relaxed is safe because the only action taken is to exit the thread - return mExitRequested.load(std::memory_order_relaxed); -} - -inline bool JobSystem::hasActiveJobs() const noexcept { - return mActiveJobs.load(std::memory_order_relaxed) > 0; -} - -inline bool JobSystem::hasJobCompleted(JobSystem::Job const* job) noexcept { - return job->runningJobCount.load(std::memory_order_acquire) <= 0; -} - -void JobSystem::wait(std::unique_lock& lock, Job* job) noexcept { - if constexpr (!DEBUG_FINISH_HANGS) { - mWaiterCondition.wait(lock); - } else { - do { - // we use a pretty long timeout (4s) so we're very confident that the system is hung - // and nothing else is happening. - std::cv_status status = mWaiterCondition.wait_for(lock, - std::chrono::milliseconds(4000)); - if (status == std::cv_status::no_timeout) { - break; - } - - // hang debugging... - - // we check of we had active jobs or if the job we're waiting on had completed already. - // there is the possibility of a race condition, but our long timeout gives us some - // confidence that we're in an incorrect state. - - auto id = getState().id; - auto activeJobs = mActiveJobs.load(); - - if (job) { - auto runningJobCount = job->runningJobCount.load(); - ASSERT_POSTCONDITION(runningJobCount > 0, - "JobSystem(%p, %d): waiting while job %p has completed and %d jobs are active!", - this, id, job, activeJobs); - } - - ASSERT_POSTCONDITION(activeJobs <= 0, - "JobSystem(%p, %d): waiting while %d jobs are active!", - this, id, activeJobs); - - } while (true); - } -} - -void JobSystem::wakeAll() noexcept { - HEAVY_SYSTRACE_CALL(); - std::lock_guard lock(mWaiterLock); - // this empty critical section is needed -- it guarantees that notify_all() happens - // after the condition's variables are set. - mWaiterCondition.notify_all(); -} - -void JobSystem::wakeOne() noexcept { - HEAVY_SYSTRACE_CALL(); - std::lock_guard lock(mWaiterLock); - // this empty critical section is needed -- it guarantees that notify_one() happens - // after the condition's variables are set. - mWaiterCondition.notify_one(); -} - -inline JobSystem::ThreadState& JobSystem::getState() noexcept { - std::lock_guard lock(mThreadMapLock); - auto iter = mThreadMap.find(std::this_thread::get_id()); - ASSERT_PRECONDITION(iter != mThreadMap.end(), "This thread has not been adopted."); - return *iter->second; -} - -JobSystem::Job* JobSystem::allocateJob() noexcept { - return mJobPool.make(); -} - -void JobSystem::put(WorkQueue& workQueue, Job* job) noexcept { - assert(job); - size_t index = job - mJobStorageBase; - assert(index >= 0 && index < MAX_JOB_COUNT); - - // put the job into the queue first - workQueue.push(uint16_t(index + 1)); - // then increase our active job count - uint32_t oldActiveJobs = mActiveJobs.fetch_add(1, std::memory_order_relaxed); - // but it's possible that the job has already been picked-up, so oldActiveJobs could be - // negative for instance. We signal only if that's not the case. - if (oldActiveJobs >= 0) { - wakeOne(); // wake-up a thread if needed... - } -} - -JobSystem::Job* JobSystem::pop(WorkQueue& workQueue) noexcept { - // decrement mActiveJobs first, this is to ensure that if there is only a single job left - // (and we're about to pick it up), other threads don't loop trying to do the same. - mActiveJobs.fetch_sub(1, std::memory_order_relaxed); - - size_t index = workQueue.pop(); - assert(index <= MAX_JOB_COUNT); - Job* job = !index ? nullptr : &mJobStorageBase[index - 1]; - - // if our guess was wrong, i.e. we couldn't pick-up a job (b/c our queue was empty), we - // need to correct mActiveJobs. - if (!job) { - if (mActiveJobs.fetch_add(1, std::memory_order_relaxed) >= 0) { - // and if there are some active jobs, then we need to wake someone up. We know it - // can't be us, because we failed taking a job and we know another thread can't - // have added one in our queue. - wakeOne(); - } - } - return job; -} - -JobSystem::Job* JobSystem::steal(WorkQueue& workQueue) noexcept { - // decrement mActiveJobs first, this is to ensure that if there is only a single job left - // (and we're about to pick it up), other threads don't loop trying to do the same. - mActiveJobs.fetch_sub(1, std::memory_order_relaxed); - - size_t index = workQueue.steal(); - assert(index <= MAX_JOB_COUNT); - Job* job = !index ? nullptr : &mJobStorageBase[index - 1]; - - // if we failed taking a job, we need to correct mActiveJobs - if (!job) { - if (mActiveJobs.fetch_add(1, std::memory_order_relaxed) >= 0) { - // and if there are some active jobs, then we need to wake someone up. We know it - // can't be us, because we failed taking a job and we know another thread can't - // have added one in our queue. - wakeOne(); - } - } - return job; -} - -inline JobSystem::ThreadState* JobSystem::getStateToStealFrom(JobSystem::ThreadState& state) noexcept { - auto& threadStates = mThreadStates; - // memory_order_relaxed is okay because we don't take any action that has data dependency - // on this value (in particular mThreadStates, is always initialized properly). - uint16_t adopted = mAdoptedThreads.load(std::memory_order_relaxed); - uint16_t const threadCount = mThreadCount + adopted; - - JobSystem::ThreadState* stateToStealFrom = nullptr; - - // don't try to steal from someone else if we're the only thread (infinite loop) - if (threadCount >= 2) { - do { - // this is biased, but frankly, we don't care. it's fast. - uint16_t index = uint16_t(state.rndGen() % threadCount); - assert(index < threadStates.size()); - stateToStealFrom = &threadStates[index]; - // don't steal from our own queue - } while (stateToStealFrom == &state); - } - return stateToStealFrom; -} - -JobSystem::Job* JobSystem::steal(JobSystem::ThreadState& state) noexcept { - HEAVY_SYSTRACE_CALL(); - Job* job = nullptr; - do { - ThreadState* const stateToStealFrom = getStateToStealFrom(state); - if (UTILS_LIKELY(stateToStealFrom)) { - job = steal(stateToStealFrom->workQueue); - } - // nullptr -> nothing to steal in that queue either, if there are active jobs, - // continue to try stealing one. - } while (!job && hasActiveJobs()); - return job; -} - -bool JobSystem::execute(JobSystem::ThreadState& state) noexcept { - HEAVY_SYSTRACE_CALL(); - - Job* job = pop(state.workQueue); - if (UTILS_UNLIKELY(job == nullptr)) { - // our queue is empty, try to steal a job - job = steal(state); - } - - if (job) { - assert(job->runningJobCount.load(std::memory_order_relaxed) >= 1); - - if (UTILS_LIKELY(job->function)) { - HEAVY_SYSTRACE_NAME("job->function"); - job->function(job->storage, *this, job); - } - finish(job); - } - return job != nullptr; -} - -void JobSystem::loop(ThreadState* state) noexcept { - setThreadName("JobSystem::loop"); - setThreadPriority(Priority::DISPLAY); - - // set a CPU affinity on each of our JobSystem thread to prevent them from jumping from core - // to core. On Android, it looks like the affinity needs to be reset from time to time. - setThreadAffinityById(state->id); - - // record our work queue - mThreadMapLock.lock(); - bool inserted = mThreadMap.emplace(std::this_thread::get_id(), state).second; - mThreadMapLock.unlock(); - ASSERT_PRECONDITION(inserted, "This thread is already in a loop."); - - // run our main loop... - do { - if (!execute(*state)) { - std::unique_lock lock(mWaiterLock); - while (!exitRequested() && !hasActiveJobs()) { - wait(lock); - setThreadAffinityById(state->id); - } - } - } while (!exitRequested()); -} - -UTILS_NOINLINE -void JobSystem::finish(Job* job) noexcept { - HEAVY_SYSTRACE_CALL(); - - bool notify = false; - - // terminate this job and notify its parent - Job* const storage = mJobStorageBase; - do { - // std::memory_order_release here is needed to synchronize with JobSystem::wait() - // which needs to "see" all changes that happened before the job terminated. - auto runningJobCount = job->runningJobCount.fetch_sub(1, std::memory_order_acq_rel); - assert(runningJobCount > 0); - if (runningJobCount == 1) { - // no more work, destroy this job and notify its parent - notify = true; - Job* const parent = job->parent == 0x7FFF ? nullptr : &storage[job->parent]; - decRef(job); - job = parent; - } else { - // there is still work (e.g.: children), we're done. - break; - } - } while (job); - - // wake-up all threads that could potentially be waiting on this job finishing - if (notify) { - wakeAll(); - } -} - -// ----------------------------------------------------------------------------------------------- -// public API... - - -JobSystem::Job* JobSystem::create(JobSystem::Job* parent, JobFunc func) noexcept { - parent = (parent == nullptr) ? mRootJob : parent; - Job* const job = allocateJob(); - if (UTILS_LIKELY(job)) { - size_t index = 0x7FFF; - if (parent) { - // add a reference to the parent to make sure it can't be terminated. - // memory_order_relaxed is safe because no action is taken at this point - // (the job is not started yet). - auto parentJobCount = parent->runningJobCount.fetch_add(1, std::memory_order_relaxed); - - // can't create a child job of a terminated parent - assert(parentJobCount > 0); - - index = parent - mJobStorageBase; - assert(index < MAX_JOB_COUNT); - } - job->function = func; - job->parent = uint16_t(index); - } - return job; -} - -void JobSystem::cancel(Job*& job) noexcept { - finish(job); - job = nullptr; -} - -JobSystem::Job* JobSystem::retain(JobSystem::Job* job) noexcept { - JobSystem::Job* retained = job; - incRef(retained); - return retained; -} - -void JobSystem::release(JobSystem::Job*& job) noexcept { - decRef(job); - job = nullptr; -} - -void JobSystem::signal() noexcept { - wakeAll(); -} - -void JobSystem::run(Job*& job) noexcept { - HEAVY_SYSTRACE_CALL(); - - ThreadState& state(getState()); - - put(state.workQueue, job); - - // after run() returns, the job is virtually invalid (it'll die on its own) - job = nullptr; -} - -JobSystem::Job* JobSystem::runAndRetain(Job* job) noexcept { - JobSystem::Job* retained = retain(job); - run(job); - return retained; -} - -void JobSystem::waitAndRelease(Job*& job) noexcept { - SYSTRACE_CALL(); - - assert(job); - assert(job->refCount.load(std::memory_order_relaxed) >= 1); - - ThreadState& state(getState()); - do { - if (!execute(state)) { - // test if job has completed first, to possibly avoid taking the lock - if (hasJobCompleted(job)) { - break; - } - - // the only way we can be here is if the job we're waiting on it being handled - // by another thread: - // - we returned from execute() which means all queues are empty - // - yet our job hasn't completed yet - // ergo, it's being run in another thread - // - // this could take time however, so we will wait with a condition, and - // continue to handle more jobs, as they get added. - - std::unique_lock lock(mWaiterLock); - if (!hasJobCompleted(job) && !hasActiveJobs() && !exitRequested()) { - wait(lock, job); - } - } - } while (!hasJobCompleted(job) && !exitRequested()); - - if (job == mRootJob) { - mRootJob = nullptr; - } - - release(job); -} - -void JobSystem::runAndWait(JobSystem::Job*& job) noexcept { - runAndRetain(job); - waitAndRelease(job); -} - -void JobSystem::adopt() { - const auto tid = std::this_thread::get_id(); - - std::unique_lock lock(mThreadMapLock); - auto iter = mThreadMap.find(tid); - ThreadState* const state = iter == mThreadMap.end() ? nullptr : iter->second; - lock.unlock(); - - if (state) { - // we're already part of a JobSystem, do nothing. - ASSERT_PRECONDITION(this == state->js, - "Called adopt on a thread owned by another JobSystem (%p), this=%p!", - state->js, this); - return; - } - - // memory_order_relaxed is safe because we don't take action on this value. - uint16_t adopted = mAdoptedThreads.fetch_add(1, std::memory_order_relaxed); - size_t index = mThreadCount + adopted; - - ASSERT_POSTCONDITION(index < mThreadStates.size(), - "Too many calls to adopt(). No more adoptable threads!"); - - // all threads adopted by the JobSystem need to run at the same priority - JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY); - - // This thread's queue will be selectable immediately (i.e.: before we set its TLS) - // however, it's not a problem since mThreadState is pre-initialized and valid - // (e.g.: the queue is empty). - - lock.lock(); - mThreadMap[tid] = &mThreadStates[index]; -} - -void JobSystem::emancipate() { - const auto tid = std::this_thread::get_id(); - std::lock_guard lock(mThreadMapLock); - auto iter = mThreadMap.find(tid); - ThreadState* const state = iter == mThreadMap.end() ? nullptr : iter->second; - ASSERT_PRECONDITION(state, "this thread is not an adopted thread"); - ASSERT_PRECONDITION(state->js == this, "this thread is not adopted by us"); - mThreadMap.erase(iter); -} - -io::ostream& operator<<(io::ostream& out, JobSystem const& js) { - for (auto const& item : js.mThreadStates) { - out << size_t(item.id) << ": " << item.workQueue.getCount() << io::endl; - } - return out; -} - -} // namespace utils diff --git a/android/src/main/cpp/StbProvider.cpp b/android/src/main/cpp/StbProvider.cpp deleted file mode 100644 index 5f796599..00000000 --- a/android/src/main/cpp/StbProvider.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include -#include - -#include - -#include -#include -#define STB_IMAGE_IMPLEMENTATION -#include - -using namespace filament; -using namespace utils; - -using std::atomic; -using std::vector; -using std::unique_ptr; - -namespace filament::gltfio { - -class StbProvider final : public TextureProvider { -public: - StbProvider(Engine* engine); - ~StbProvider(); - - Texture* pushTexture(const uint8_t* data, size_t byteCount, - const char* mimeType, uint64_t flags) final; - - Texture* popTexture() final; - void updateQueue() final; - void waitForCompletion() final; - void cancelDecoding() final; - const char* getPushMessage() const final; - const char* getPopMessage() const final; - size_t getPushedCount() const final { return mPushedCount; } - size_t getPoppedCount() const final { return mPoppedCount; } - size_t getDecodedCount() const final { return mDecodedCount; } - -private: - enum class TextureState { - DECODING, // Texture has been pushed, mipmap levels are not yet complete. - READY, // Mipmap levels are available but texture has not been popped yet. - POPPED, // Client has popped the texture from the queue. - }; - - struct TextureInfo { - Texture* texture; - TextureState state; - atomic decodedTexelsBaseMipmap; - vector sourceBuffer; - JobSystem::Job* decoderJob; - }; - - // Declare some sentinel values for the "decodedTexelsBaseMipmap" field. - // Note that the "state" field can be modified only on the foreground thread. - static const intptr_t DECODING_NOT_READY = 0x0; - static const intptr_t DECODING_ERROR = 0x1; - - void decodeSingleTexture(); - - size_t mPushedCount = 0; - size_t mPoppedCount = 0; - size_t mDecodedCount = 0; - vector > mTextures; - JobSystem::Job* mDecoderRootJob; - std::string mRecentPushMessage; - std::string mRecentPopMessage; - Engine* const mEngine; -}; - -Texture* StbProvider::pushTexture(const uint8_t* data, size_t byteCount, - const char* mimeType, FlagBits flags) { - int width, height, numComponents; - if (!stbi_info_from_memory(data, byteCount, &width, &height, &numComponents)) { - mRecentPushMessage = std::string("Unable to parse texture: ") + stbi_failure_reason(); - return nullptr; - } - - using InternalFormat = Texture::InternalFormat; - const FlagBits sRGB = FlagBits(Flags::sRGB); - - Texture* texture = Texture::Builder() - .width(width) - .height(height) - .levels(0xff) - .format((flags & sRGB) ? InternalFormat::SRGB8_A8 : InternalFormat::RGBA8) - .build(*mEngine); - - if (texture == nullptr) { - mRecentPushMessage = "Unable to build Texture object."; - return nullptr; - } - - mRecentPushMessage.clear(); - TextureInfo* info = mTextures.emplace_back(new TextureInfo).get(); - ++mPushedCount; - - info->texture = texture; - info->state = TextureState::DECODING; - info->sourceBuffer.assign(data, data + byteCount); - info->decodedTexelsBaseMipmap.store(DECODING_NOT_READY); - - // On single threaded systems, it is usually fine to create jobs because the job system will - // simply execute serially. However in our case, we wish to amortize the decoder cost across - // several frames, so we instead use the updateQueue() method to perform decoding. - if constexpr (!UTILS_HAS_THREADING) { - info->decoderJob = nullptr; - return texture; - } - - JobSystem* js = &mEngine->getJobSystem(); - info->decoderJob = jobs::createJob(*js, mDecoderRootJob, [this, info] { - auto& source = info->sourceBuffer; - int width, height, comp; - - // Test asynchronous loading by uncommenting this line. - // std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10000)); - - stbi_uc* texels = stbi_load_from_memory(source.data(), source.size(), - &width, &height, &comp, 4); - source.clear(); - source.shrink_to_fit(); - info->decodedTexelsBaseMipmap.store(texels ? intptr_t(texels) : DECODING_ERROR); - }); - - js->runAndRetain(info->decoderJob); - return texture; -} - -Texture* StbProvider::popTexture() { - // We don't bother shrinking the mTextures vector here, instead we periodically clean it up in - // the updateQueue method, since popTexture is typically called more frequently. Textures - // can become ready in non-deterministic order due to concurrency. - for (auto& texture : mTextures) { - if (texture->state == TextureState::READY) { - texture->state = TextureState::POPPED; - ++mPoppedCount; - const intptr_t ptr = texture->decodedTexelsBaseMipmap.load(); - if (ptr == DECODING_ERROR || ptr == DECODING_NOT_READY) { - mRecentPopMessage = "Texture is incomplete"; - } else { - mRecentPopMessage.clear(); - } - return texture->texture; - } - } - return nullptr; -} - -void StbProvider::updateQueue() { - if (!UTILS_HAS_THREADING) { - decodeSingleTexture(); - } - JobSystem* js = &mEngine->getJobSystem(); - for (auto& info : mTextures) { - if (info->state != TextureState::DECODING) { - continue; - } - Texture* texture = info->texture; - if (intptr_t data = info->decodedTexelsBaseMipmap.load()) { - if (info->decoderJob) { - js->waitAndRelease(info->decoderJob); - } - if (data == DECODING_ERROR) { - info->state = TextureState::READY; - ++mDecodedCount; - continue; - } - Texture::PixelBufferDescriptor pbd((uint8_t*) data, - texture->getWidth() * texture->getHeight() * 4, Texture::Format::RGBA, - Texture::Type::UBYTE, [](void* mem, size_t, void*) { stbi_image_free(mem); }); - texture->setImage(*mEngine, 0, std::move(pbd)); - - // Call generateMipmaps unconditionally to fulfill the promise of the TextureProvider - // interface. Providers of hierarchical images (e.g. KTX) call this only if needed. - texture->generateMipmaps(*mEngine); - - info->state = TextureState::READY; - ++mDecodedCount; - } - } - - // Here we periodically clean up the "queue" (which is really just a vector) by removing unused - // items from the front. This might ignore a popped texture that occurs in the middle of the - // vector, but that's okay, it will be cleaned up eventually. - decltype(mTextures)::iterator last = mTextures.begin(); - while (last != mTextures.end() && (*last)->state == TextureState::POPPED) ++last; - mTextures.erase(mTextures.begin(), last); -} - -void StbProvider::waitForCompletion() { - JobSystem& js = mEngine->getJobSystem(); - for (auto& info : mTextures) { - if (info->decoderJob) { - js.waitAndRelease(info->decoderJob); - } - } -} - -void StbProvider::cancelDecoding() { - // TODO: Currently, StbProvider runs jobs eagerly and JobSystem does not allow cancellation of - // in-flight jobs. We should consider throttling the number of simultaneous decoder jobs, which - // would allow for actual cancellation. - waitForCompletion(); -} - -const char* StbProvider::getPushMessage() const { - return mRecentPushMessage.empty() ? nullptr : mRecentPushMessage.c_str(); -} - -const char* StbProvider::getPopMessage() const { - return mRecentPopMessage.empty() ? nullptr : mRecentPopMessage.c_str(); -} - -void StbProvider::decodeSingleTexture() { - assert_invariant(!UTILS_HAS_THREADING); - for (auto& info : mTextures) { - if (info->state == TextureState::DECODING) { - auto& source = info->sourceBuffer; - int width, height, comp; - stbi_uc* texels = stbi_load_from_memory(source.data(), source.size(), - &width, &height, &comp, 4); - source.clear(); - source.shrink_to_fit(); - info->decodedTexelsBaseMipmap.store(texels ? intptr_t(texels) : DECODING_ERROR); - break; - } - } -} - -StbProvider::StbProvider(Engine* engine) : mEngine(engine) { - mDecoderRootJob = mEngine->getJobSystem().createJob(); -} - -StbProvider::~StbProvider() { - cancelDecoding(); - mEngine->getJobSystem().release(mDecoderRootJob); -} - -TextureProvider* createStbProvider(Engine* engine) { - return new StbProvider(engine); -} - -} // namespace filament::gltfio diff --git a/android/src/main/cpp/filament_android.cpp b/android/src/main/cpp/filament_android.cpp deleted file mode 100644 index 5f53bbde..00000000 --- a/android/src/main/cpp/filament_android.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "FilamentViewer.hpp" -#include "SceneAsset.hpp" -#include "ResourceBuffer.hpp" -#include -#include -#include -#include -#include - -#include - -using namespace polyvox; -using namespace std; - -static AAssetManager* am; -static map _apk_assets; -static map _file_assets; -static uint32_t _i = 0; - -static ResourceBuffer loadResource(const char* name) { - - - string name_str(name); - auto id = _i++; - - if (name_str.rfind("file://", 0) == 0) { - streampos length; - ifstream is(name_str.substr(7), ios::binary); - is.seekg (0, ios::end); - length = is.tellg(); - char * buffer; - buffer = new char [length]; - is.seekg (0, ios::beg); - is.read (buffer, length); - is.close(); - _file_assets[id] = buffer; - return ResourceBuffer(buffer, length, id); - } else { - AAsset *asset = AAssetManager_open(am, name, AASSET_MODE_BUFFER); - if(asset == nullptr) { - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "Couldn't locate asset [ %s ]", name); - return ResourceBuffer(nullptr, 0, 0); - } - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "Loading asset [ %s ]", name); - - off_t length = AAsset_getLength(asset); - const void * buffer = AAsset_getBuffer(asset); - - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "Read [ %lu ] bytes into buffer", length); - - _apk_assets[id] = asset; - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "Loaded asset [ %s ] of length %zu at index %d", name, length, id); - return ResourceBuffer(buffer, length, id); - } -} - -static void freeResource(uint32_t id) { - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "Freeing loaded resource at index [ %d ] ", id); - auto apk_it = _apk_assets.find(id); - if (apk_it != _apk_assets.end()) { - AAsset_close(apk_it->second); - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "Closed Android asset"); - } else { - auto file_it = _file_assets.find(id); - if (file_it != _file_assets.end()) { - free(file_it->second); - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "Freed asset from filesystem."); - } else { - __android_log_print(ANDROID_LOG_VERBOSE, "filament_api", "FATAL - could not find Android or filesystem (hot reload) asset under id %d", id); - } - } - -} - -extern "C" { - - void* create_filament_viewer_android( - jobject surface, - JNIEnv* env, - jobject assetManager - ) { - ANativeWindow* layer = ANativeWindow_fromSurface(env, surface); - am = AAssetManager_fromJava(env, assetManager); - return new FilamentViewer((void*)layer, loadResource, freeResource); - } - - - - -} diff --git a/android/src/main/java/com/google/android/filament/SwapChain.java b/android/src/main/java/com/google/android/filament/SwapChain.java deleted file mode 100644 index 391f1f6e..00000000 --- a/android/src/main/java/com/google/android/filament/SwapChain.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.filament; - -import androidx.annotation.NonNull; - -/** - * A SwapChain represents an Operating System's native renderable surface. - * - *

Typically it's a native window or a view. Because a SwapChain is initialized - * from a native object, it is given to filament as an Object, which must be of the - * proper type for each platform filament is running on.

- * - * - * SwapChain swapChain = engine.createSwapChain(nativeWindow); - * - * - *

The nativeWindow parameter above must be of type:

- * - *
- * - * - * - *
Platform nativeWindow type
Android {@link android.view.Surface Surface}
- *
- *

- * - *

Examples

- * - *

Android

- * - * - *

A {@link android.view.Surface Surface} can be retrieved from a - * {@link android.view.SurfaceView SurfaceView} or {@link android.view.SurfaceHolder SurfaceHolder} - * easily using {@link android.view.SurfaceHolder#getSurface SurfaceHolder.getSurface()} and/or - * {@link android.view.SurfaceView#getHolder SurfaceView.getHolder()}.

- * - *

To use a {@link android.view.TextureView Textureview} as a SwapChain, it is - * necessary to first get its {@link android.graphics.SurfaceTexture SurfaceTexture}, - * for instance using {@link android.view.TextureView.SurfaceTextureListener SurfaceTextureListener} - * and then create a {@link android.view.Surface Surface}:

- * - *
- *  // using a TextureView.SurfaceTextureListener:
- *  public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
- *      mSurface = new Surface(surfaceTexture);
- *      // mSurface can now be used with Engine.createSwapChain()
- *  }
- * 
- * - * @see Engine - */ -public class SwapChain { - private final Object mSurface; - private long mNativeObject; - - public static final long CONFIG_DEFAULT = 0x0; - - /** - * This flag indicates that the SwapChain must be allocated with an - * alpha-channel. - */ - public static final long CONFIG_TRANSPARENT = 0x1; - - /** - * This flag indicates that the SwapChain may be used as a source surface - * for reading back render results. This config must be set when creating - * any SwapChain that will be used as the source for a blit operation. - * - * @see Renderer#copyFrame - */ - public static final long CONFIG_READABLE = 0x2; - - /** - * Indicates that the native X11 window is an XCB window rather than an XLIB window. - * This is ignored on non-Linux platforms and in builds that support only one X11 API. - */ - public static final long CONFIG_ENABLE_XCB = 0x4; - - SwapChain(long nativeSwapChain, Object surface) { - mNativeObject = nativeSwapChain; - mSurface = surface; - } - - /** - * @return the native Object this SwapChain was created from or null - * for a headless SwapChain. - */ - public Object getNativeWindow() { - return mSurface; - } - - /** - * FrameCompletedCallback is a callback function that notifies an application when a frame's - * contents have completed rendering on the GPU. - * - *

- * Use setFrameCompletedCallback to set a callback on an individual SwapChain. Each time a frame - * completes GPU rendering, the callback will be called. - *

- * - *

- * The FrameCompletedCallback is guaranteed to be called on the main Filament thread. - *

- * - *

- * Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the - * callback (which will never be called) and proceed normally. - *

- * - * @param handler A {@link java.util.concurrent.Executor Executor}. - * @param callback The Runnable callback to invoke. - */ - public void setFrameCompletedCallback(@NonNull Object handler, @NonNull Runnable callback) { - nSetFrameCompletedCallback(getNativeObject(), handler, callback); - } - - public long getNativeObject() { - if (mNativeObject == 0) { - throw new IllegalStateException("Calling method on destroyed SwapChain"); - } - return mNativeObject; - } - - void clearNativeObject() { - mNativeObject = 0; - } - - private static native void nSetFrameCompletedCallback(long nativeSwapChain, Object handler, Runnable callback); -} diff --git a/android/src/main/java/com/google/android/filament/android/DisplayHelper.java b/android/src/main/java/com/google/android/filament/android/DisplayHelper.java deleted file mode 100644 index 3fc1f5e3..00000000 --- a/android/src/main/java/com/google/android/filament/android/DisplayHelper.java +++ /dev/null @@ -1,202 +0,0 @@ -// /* -// * Copyright (C) 2020 The Android Open Source Project -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ - -// package com.google.android.filament.android; - -// import android.content.Context; -// import android.hardware.display.DisplayManager; -// import android.os.Build; -// import android.os.Handler; -// import android.view.Display; - -// import com.google.android.filament.Renderer; - -// import androidx.annotation.NonNull; -// import androidx.annotation.Nullable; - -// /** -// * DisplayHelper is here to help managing a Display, for instance being notified when its -// * resolution or refresh rate changes. -// */ -// public class DisplayHelper { - -// private Handler mHandler = null; -// private DisplayManager mDisplayManager; -// private Display mDisplay; -// private Renderer mRenderer; -// private DisplayManager.DisplayListener mListener; - -// /** -// * Creates a DisplayHelper which helps managing a {@link Display}. -// * -// * The {@link Display} to manage is specified with {@link #attach} -// * -// * @param context a {@link Context} to used to retrieve the {@link DisplayManager} -// */ -// public DisplayHelper(@NonNull Context context) { -// mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); -// } - -// /** -// * Creates a DisplayHelper which helps manage a {@link Display} and provides a Handler -// * where callbacks can execute filament code. Use this method if filament is executing -// * on another thread. -// * -// * @param context a {@link Context} to used to retrieve teh {@link DisplayManager} -// * @param handler a {@link Handler} used to run callbacks accessing filament -// */ -// public DisplayHelper(@NonNull Context context, @NonNull Handler handler) { -// this(context); -// mHandler = handler; -// } - -// @Override -// protected void finalize() throws Throwable { -// try { -// // just for safety -// detach(); -// } finally { -// super.finalize(); -// } -// } - -// /** -// * Sets the filament {@link Renderer} associated to the {@link Display}, from this point -// * on, {@link Renderer.DisplayInfo} will be automatically updated when the {@link Display} -// * properties change. -// * -// * This is typically called from {@link UiHelper.RendererCallback#onNativeWindowChanged}. -// * -// * @param renderer a filament {@link Renderer} instance -// * @param display a {@link Display} to be associated with the {@link Renderer} -// */ -// public void attach(@NonNull Renderer renderer, @NonNull Display display) { -// if (renderer == mRenderer && display == mDisplay) { -// return; -// } -// mRenderer = renderer; -// mDisplay = display; -// mListener = new DisplayManager.DisplayListener() { -// @Override -// public void onDisplayAdded(int displayId) { -// } -// @Override -// public void onDisplayRemoved(int displayId) { -// } -// @Override -// public void onDisplayChanged(int displayId) { -// if (displayId == display.getDisplayId()) { -// updateDisplayInfo(); -// } -// } -// }; -// mDisplayManager.registerDisplayListener(mListener, mHandler); - -// // always invoke the callback when it's registered -// if (mHandler != null) { -// mHandler.post(new Runnable() { -// @Override -// public void run() { -// updateDisplayInfo(); -// } -// }); -// } else { -// updateDisplayInfo(); -// } -// } - -// /** -// * Disconnect the previously set {@link Renderer} from {@link Display} -// * This is typically called from {@link UiHelper.RendererCallback#onDetachedFromSurface}. -// */ -// public void detach() { -// if (mListener != null) { -// mDisplayManager.unregisterDisplayListener(mListener); -// mListener = null; -// mDisplay = null; -// mRenderer = null; -// } -// } - -// private void updateDisplayInfo() { -// mRenderer.setDisplayInfo( -// DisplayHelper.getDisplayInfo(mDisplay, mRenderer.getDisplayInfo())); -// } - -// /** -// * Returns the {@link Display} currently monitored -// * @return the {@link Display} set in {@link #attach} or null -// */ -// public Display getDisplay() { -// return mDisplay; -// } - -// /** -// * Populate a {@link Renderer.DisplayInfo} with properties from the given {@link Display} -// * -// * @param display {@link Display} to get {@link Renderer.DisplayInfo} from -// * @param info an instance of {@link Renderer.DisplayInfo} or null -// * @return an populated instance of {@link Renderer.DisplayInfo} -// */ -// @NonNull -// public static Renderer.DisplayInfo getDisplayInfo(@NonNull Display display, @Nullable Renderer.DisplayInfo info) { -// if (info == null) { -// info = new Renderer.DisplayInfo(); -// } -// info.refreshRate = DisplayHelper.getRefreshRate(display); -// return info; -// } - -// /** -// * @return the {@link Display} application vsync offset 0 if not supported -// * @see Display#getAppVsyncOffsetNanos -// */ -// public static long getAppVsyncOffsetNanos(@NonNull Display display) { -// if (Build.VERSION.SDK_INT >= 29) { -// return display.getAppVsyncOffsetNanos(); -// } -// return 0; -// } - -// /** -// * @return the {@link Display} presentation deadline before the h/w vsync event in nanoseconds -// * @see Display#getPresentationDeadlineNanos -// */ -// public static long getPresentationDeadlineNanos(@NonNull Display display) { -// if (Build.VERSION.SDK_INT >= 29) { -// return display.getPresentationDeadlineNanos(); -// } -// // not supported, pick something reasonable -// return 11_600_000; -// } - -// /** -// * @return the {@link Display} refresh rate in Hz -// * @see Display#getRefreshRate -// */ -// public static float getRefreshRate(@NonNull Display display) { -// return display.getRefreshRate(); -// } - -// /** -// * Returns a {@link Display}'s refresh period in nanoseconds -// * @param display the {@link Display} to get the refresh period from -// * @return the {@link Display} refresh period in nanoseconds -// */ -// public static long getRefreshPeriodNanos(@NonNull Display display) { -// return (long) (1_000_000_000.0 / display.getRefreshRate()); -// } -// } diff --git a/android/src/main/java/com/google/android/filament/android/UiHelper.java b/android/src/main/java/com/google/android/filament/android/UiHelper.java deleted file mode 100644 index ae1bdcd7..00000000 --- a/android/src/main/java/com/google/android/filament/android/UiHelper.java +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.filament.android; - -import android.graphics.PixelFormat; -import android.graphics.SurfaceTexture; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.TextureView; - -import com.google.android.filament.SwapChain; - -/** - * UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder - * so it can be used to render into with Filament. - * - * Here is a simple example with a SurfaceView. The code would be exactly the same with a - * TextureView: - * - *
- * public class FilamentActivity extends Activity {
- *     private UiHelper mUiHelper;
- *     private SurfaceView mSurfaceView;
- *
- *     // Filament specific APIs
- *     private Engine mEngine;
- *     private Renderer mRenderer;
- *     private View mView; // com.google.android.filament.View, not android.view.View
- *     private SwapChain mSwapChain;
- *
- *     public void onCreate(Bundle savedInstanceState) {
- *         super.onCreate(savedInstanceState);
- *
- *         // Create a SurfaceView and add it to the activity
- *         mSurfaceView = new SurfaceView(this);
- *         setContentView(mSurfaceView);
- *
- *         // Create the Filament UI helper
- *         mUiHelper = new UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK);
- *
- *         // Attach the SurfaceView to the helper, you could do the same with a TextureView
- *         mUiHelper.attachTo(mSurfaceView);
- *
- *         // Set a rendering callback that we will use to invoke Filament
- *         mUiHelper.setRenderCallback(new UiHelper.RendererCallback() {
- *             public void onNativeWindowChanged(Surface surface) {
- *                 if (mSwapChain != null) mEngine.destroySwapChain(mSwapChain);
- *                 mSwapChain = mEngine.createSwapChain(surface, mUiHelper.getSwapChainFlags());
- *             }
- *
- *             // The native surface went away, we must stop rendering.
- *             public void onDetachedFromSurface() {
- *                 if (mSwapChain != null) {
- *                     mEngine.destroySwapChain(mSwapChain);
- *
- *                     // Required to ensure we don't return before Filament is done executing the
- *                     // destroySwapChain command, otherwise Android might destroy the Surface
- *                     // too early
- *                     mEngine.flushAndWait();
- *
- *                     mSwapChain = null;
- *                 }
- *             }
- *
- *             // The native surface has changed size. This is always called at least once
- *             // after the surface is created (after onNativeWindowChanged() is invoked).
- *             public void onResized(int width, int height) {
- *                 // Compute camera projection and set the viewport on the view
- *             }
- *         });
- *
- *         mEngine = Engine.create();
- *         mRenderer = mEngine.createRenderer();
- *         mView = mEngine.createView();
- *         // Create scene, camera, etc.
- *     }
- *
- *     public void onDestroy() {
- *         super.onDestroy();
- *         // Always detach the surface before destroying the engine
- *         mUiHelper.detach();
- *
- *         mEngine.destroy();
- *     }
- *
- *     // This is an example of a render function. You will most likely invoke this from
- *     // a Choreographer callback to trigger rendering at vsync.
- *     public void render() {
- *         if (mUiHelper.isReadyToRender) {
- *             // If beginFrame() returns false you should skip the frame
- *             // This means you are sending frames too quickly to the GPU
- *             if (mRenderer.beginFrame(swapChain)) {
- *                 mRenderer.render(mView);
- *                 mRenderer.endFrame();
- *             }
- *         }
- *     }
- * }
- * 
- */ -public class UiHelper { - private static final String LOG_TAG = "UiHelper"; - private static final boolean LOGGING = false; - - private int mDesiredWidth; - private int mDesiredHeight; - private Object mNativeWindow; - - private RendererCallback mRenderCallback; - private boolean mHasSwapChain; - - private RenderSurface mRenderSurface; - - private boolean mOpaque = true; - private boolean mOverlay = false; - - /** - * Enum used to decide whether UiHelper should perform extra error checking. - * - * @see UiHelper#UiHelper(ContextErrorPolicy) - */ - public enum ContextErrorPolicy { - /** Check for extra errors. */ - CHECK, - /** Do not check for extra errors. */ - DONT_CHECK - } - - /** - * Interface used to know when the native surface is created, destroyed or resized. - * - * @see #setRenderCallback(RendererCallback) - */ - public interface RendererCallback { - /** - * Called when the underlying native window has changed. - */ - void onNativeWindowChanged(Surface surface); - - /** - * Called when the surface is going away. After this call isReadyToRender() - * returns false. You MUST have stopped drawing when returning. - * This is called from detach() or if the surface disappears on its own. - */ - void onDetachedFromSurface(); - - /** - * Called when the underlying native window has been resized. - */ - void onResized(int width, int height); - } - - private interface RenderSurface { - void resize(int width, int height); - void detach(); - } - - private static class SurfaceViewHandler implements RenderSurface { - private SurfaceView mSurfaceView; - - SurfaceViewHandler(SurfaceView surface) { - mSurfaceView = surface; - } - - @Override - public void resize(int width, int height) { - mSurfaceView.getHolder().setFixedSize(width, height); - } - - @Override - public void detach() { - } - } - - private static class SurfaceHolderHandler implements RenderSurface { - private SurfaceHolder mSurfaceHolder; - - SurfaceHolderHandler(SurfaceHolder surface) { - mSurfaceHolder = surface; - } - - @Override - public void resize(int width, int height) { - mSurfaceHolder.setFixedSize(width, height); - } - - @Override - public void detach() { - } - } - - private class TextureViewHandler implements RenderSurface { - private TextureView mTextureView; - private Surface mSurface; - - TextureViewHandler(TextureView surface) { mTextureView = surface; } - - @Override - public void resize(int width, int height) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); - } - // the call above won't cause TextureView.onSurfaceTextureSizeChanged() - mRenderCallback.onResized(width, height); - } - - @Override - public void detach() { - setSurface(null); - } - - void setSurface(Surface surface) { - if (surface == null) { - if (mSurface != null) { - mSurface.release(); - } - } - mSurface = surface; - } - } - - /** - * Creates a UiHelper which will help manage the native surface provided by a - * SurfaceView or a TextureView. - */ - public UiHelper() { - this(ContextErrorPolicy.CHECK); - } - - /** - * Creates a UiHelper which will help manage the native surface provided by a - * SurfaceView or a TextureView. - * - * @param policy The error checking policy to use. - */ - public UiHelper(ContextErrorPolicy policy) { - // TODO: do something with policy - } - - /** - * Sets the renderer callback that will be notified when the native surface is - * created, destroyed or resized. - * - * @param renderCallback The callback to register. - */ - public void setRenderCallback(@Nullable RendererCallback renderCallback) { - mRenderCallback = renderCallback; - } - - /** - * Returns the current render callback associated with this UiHelper. - */ - @Nullable - public RendererCallback getRenderCallback() { - return mRenderCallback; - } - - /** - * Free resources associated to the native window specified in {@link #attachTo(SurfaceView)}, - * {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}. - */ - public void detach() { - destroySwapChain(); - mNativeWindow = null; - mRenderSurface = null; - } - - /** - * Checks whether we are ready to render into the attached surface. - * - * Using OpenGL ES when this returns true, will result in drawing commands being lost, - * HOWEVER, GLES state will be preserved. This is useful to initialize the engine. - * - * @return true: rendering is possible, false: rendering is not possible. - */ - public boolean isReadyToRender() { - return mHasSwapChain; - } - - /** - * Set the size of the render target buffers of the native surface. - */ - public void setDesiredSize(int width, int height) { - mDesiredWidth = width; - mDesiredHeight = height; - if (mRenderSurface != null) { - mRenderSurface.resize(width, height); - } - } - - /** - * Returns the requested width for the native surface. - */ - public int getDesiredWidth() { - return mDesiredWidth; - } - - /** - * Returns the requested height for the native surface. - */ - public int getDesiredHeight() { - return mDesiredHeight; - } - - /** - * Returns true if the render target is opaque. - */ - public boolean isOpaque() { - return mOpaque; - } - - /** - * Controls whether the render target (SurfaceView or TextureView) is opaque or not. - * The render target is considered opaque by default. - * - * Must be called before calling {@link #attachTo(SurfaceView)}, {@link #attachTo(TextureView)}, - * or {@link #attachTo(SurfaceHolder)}. - * - * @param opaque Indicates whether the render target should be opaque. True by default. - */ - public void setOpaque(boolean opaque) { - mOpaque = opaque; - } - - /** - * Returns true if the SurfaceView used as a render target should be positioned above - * other surfaces but below the activity's surface. False by default. - */ - public boolean isMediaOverlay() { - return mOverlay; - } - - /** - * Controls whether the surface of the SurfaceView used as a render target should be - * positioned above other surfaces but below the activity's surface. This property - * only has an effect when used in combination with {@link #setOpaque(boolean) setOpaque(false)} - * and does not affect TextureView targets. - * - * Must be called before calling {@link #attachTo(SurfaceView)} - * or {@link #attachTo(TextureView)}. - * - * Has no effect when using {@link #attachTo(SurfaceHolder)}. - * - * @param overlay Indicates whether the render target should be rendered below the activity's - * surface when transparent. - */ - public void setMediaOverlay(boolean overlay) { - mOverlay = overlay; - } - - /** - * Returns the flags to pass to - * {@link com.google.android.filament.Engine#createSwapChain(Object, long)} to honor all - * the options set on this UiHelper. - */ - public long getSwapChainFlags() { - return isOpaque() ? SwapChain.CONFIG_DEFAULT : SwapChain.CONFIG_TRANSPARENT; - } - - /** - * Associate UiHelper with a SurfaceView. - * - * As soon as SurfaceView is ready (i.e. has a Surface), we'll create the - * EGL resources needed, and call user callbacks if needed. - */ - public void attachTo(@NonNull SurfaceView view) { - if (attach(view)) { - boolean translucent = !isOpaque(); - // setZOrderOnTop() and setZOrderMediaOverlay() override each other, - // we must only call one of them - if (isMediaOverlay()) { - view.setZOrderMediaOverlay(translucent); - } else { - view.setZOrderOnTop(translucent); - } - - int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; - view.getHolder().setFormat(format); - - mRenderSurface = new SurfaceViewHandler(view); - - final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); - createSwapChain(holder.getSurface()); - } - - @Override - public void surfaceChanged( - SurfaceHolder holder, int format, int width, int height) { - // Note: this is always called at least once after surfaceCreated() - if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); - mRenderCallback.onResized(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); - destroySwapChain(); - } - }; - - SurfaceHolder holder = view.getHolder(); - holder.addCallback(callback); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - holder.setFixedSize(mDesiredWidth, mDesiredHeight); - } - - // in case the SurfaceView's surface already existed - final Surface surface = holder.getSurface(); - if (surface != null && surface.isValid()) { - callback.surfaceCreated(holder); - callback.surfaceChanged(holder, format, - holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); - } - } - } - - /** - * Associate UiHelper with a TextureView. - * - * As soon as TextureView is ready (i.e. has a buffer), we'll create the - * EGL resources needed, and call user callbacks if needed. - */ - public void attachTo(@NonNull TextureView view) { - if (attach(view)) { - view.setOpaque(isOpaque()); - - mRenderSurface = new TextureViewHandler(view); - - TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable( - SurfaceTexture surfaceTexture, int width, int height) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); - } - } - - Surface surface = new Surface(surfaceTexture); - TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface; - textureViewHandler.setSurface(surface); - - createSwapChain(surface); - - // Call this the first time because onSurfaceTextureSizeChanged() - // isn't called at initialization time - mRenderCallback.onResized(width, height); - } - - @Override - public void onSurfaceTextureSizeChanged( - SurfaceTexture surfaceTexture, int width, int height) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()"); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); - mRenderCallback.onResized(mDesiredWidth, mDesiredHeight); - } else { - mRenderCallback.onResized(width, height); - } - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()"); - destroySwapChain(); - return true; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { } - }; - - view.setSurfaceTextureListener(listener); - - // in case the View's SurfaceTexture already existed - if (view.isAvailable()) { - SurfaceTexture surfaceTexture = view.getSurfaceTexture(); - listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight); - } - } - } - - /** - * Associate UiHelper with a SurfaceHolder. - * - * As soon as a Surface is created, we'll create the - * EGL resources needed, and call user callbacks if needed. - */ - public void attachTo(@NonNull SurfaceHolder holder) { - if (attach(holder)) { - int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; - holder.setFormat(format); - - mRenderSurface = new SurfaceHolderHandler(holder); - - final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder surfaceHolder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); - createSwapChain(holder.getSurface()); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - // Note: this is always called at least once after surfaceCreated() - if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); - mRenderCallback.onResized(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); - destroySwapChain(); - } - }; - - holder.addCallback(callback); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - holder.setFixedSize(mDesiredWidth, mDesiredHeight); - } - - // in case the SurfaceHolder's surface already existed - final Surface surface = holder.getSurface(); - if (surface != null && surface.isValid()) { - callback.surfaceCreated(holder); - callback.surfaceChanged(holder, format, - holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); - } - } - } - - private boolean attach(@NonNull Object nativeWindow) { - if (mNativeWindow != null) { - // we are already attached to a native window - if (mNativeWindow == nativeWindow) { - // nothing to do - return false; - } - destroySwapChain(); - } - mNativeWindow = nativeWindow; - return true; - } - - private void createSwapChain(@NonNull Surface surface) { - mRenderCallback.onNativeWindowChanged(surface); - mHasSwapChain = true; - } - - private void destroySwapChain() { - if (mRenderSurface != null) { - mRenderSurface.detach(); - } - mRenderCallback.onDetachedFromSurface(); - mHasSwapChain = false; - } -} diff --git a/android/src/main/kotlin/app/polyvox/filament/FilamentInterop.kt b/android/src/main/kotlin/app/polyvox/filament/FilamentInterop.kt index af13fb4e..9e6921d2 100644 --- a/android/src/main/kotlin/app/polyvox/filament/FilamentInterop.kt +++ b/android/src/main/kotlin/app/polyvox/filament/FilamentInterop.kt @@ -1,105 +1,102 @@ package app.polyvox.filament -import com.sun.jna.Library -import com.sun.jna.Native -import com.sun.jna.Pointer import com.sun.jna.ptr.PointerByReference import com.sun.jna.ptr.IntByReference -import com.sun.jna.Structure -import com.sun.jna.NativeLibrary -import com.sun.jna.StringArray -import com.sun.jna.JNIEnv import android.view.Surface -import android.content.res.AssetManager +import android.content.res.AssetManager +import com.sun.jna.* -import java.nio.ByteBuffer - -interface FilamentInterop : Library { - - fun create_filament_viewer_android( - layer:Object, - env:JNIEnv, - am:AssetManager - ) : Pointer; - - fun delete_filament_viewer( - viewer:Pointer, - ) : Pointer; - - fun load_skybox(viewer:Pointer, skyboxPath:String) : Pointer; - - fun load_ibl(viewer:Pointer, skyboxPath:String) : Pointer; - - fun load_glb(viewer:Pointer, uri:String) : Pointer; - - fun load_gltf(viewer:Pointer, uri:String, relativeResourcePath:String) : Pointer; - - fun set_camera(viewer:Pointer, asset:Pointer, nodeName:String) : Boolean; - - fun render(viewer:Pointer, frameTimeInNanos:Long); - - fun set_frame_interval(viewer:Pointer, interval:Float); - - fun create_swap_chain(viewer:Pointer, surface:Surface, env:JNIEnv); - - fun destroy_swap_chain(viewer:Pointer); - - fun update_viewport_and_camera_projection(viewer:Pointer, width:Int, height:Int, scaleFactor:Float); - - fun scroll_begin(viewer:Pointer); - fun scroll_update(viewer:Pointer, x:Float, y:Float, delta:Float); - fun scroll_end(viewer:Pointer); - - fun grab_begin(viewer:Pointer, x:Float, y:Float, pan:Boolean) - - fun grab_update(viewer:Pointer, x:Float, y:Float) - - fun grab_end(viewer:Pointer) - - fun apply_weights(asset:Pointer, weights:FloatArray, size:Int); - - fun set_animation(asset:Pointer, frames:FloatArray, numWeights:Int, numFrames:Int, frameRate:Float); - - fun get_morph_target_name_count(asset:Pointer, meshName:String) : Int; - - fun get_morph_target_name(asset:Pointer, meshName:String, outPtr:Pointer, index:Int); - - fun get_animation_count(asset:Pointer) : Int; - fun get_animation_name(asset:Pointer, outPtr:Pointer, index:Int); - - fun play_animation(asset:Pointer, index:Int, loop:Boolean, reverse:Boolean); - fun stop_animation(asset:Pointer, index:Int); - - fun free_pointer(ptr:Pointer, size:Int); - - fun remove_asset(viewer:Pointer, asset:Pointer); - - fun clear_assets(viewer:Pointer); - - fun remove_skybox(viewer:Pointer); - fun remove_ibl(viewer:Pointer); - - fun add_light(viewer:Pointer, type:Int, colour:Float, intensity:Float, posX:Float, posY:Float, posZ:Float, dirX:Float, dirY:Float, dirZ:Float, shadows:Boolean) : Int; - fun remove_light(viewer:Pointer, entityId:Int); - fun clear_lights(viewer:Pointer); - - fun set_background_image(viewer:Pointer, path:String); - fun set_background_image_position(viewer:Pointer, x:Float, y:Float, clamp:Boolean); - - fun load_texture(asset:Pointer, path:String, renderableIndex:Int); - fun set_texture(asset:Pointer); - - fun transform_to_unit_cube(asset:Pointer); - - fun set_position(asset:Pointer, x:Float, y:Float, z:Float); - fun set_rotation(asset:Pointer, rads:Float, x:Float, y:Float, z:Float); - - fun set_camera_position(asset:Pointer, x:Float, y:Float, z:Float); - fun set_camera_rotation(asset:Pointer, rads:Float, x:Float, y:Float, z:Float); - fun set_camera_focal_length(asset:Pointer, focalLength:Float); - fun set_camera_focus_distance(asset:Pointer, focusDistance:Float); - fun set_scale(asset:Pointer, scale:Float); +open class ResourceBuffer: Structure(), Structure.ByValue { + @JvmField var data: Pointer = Pointer(0); + @JvmField var size: Int = 0; + @JvmField var id: Int = 0; + override fun getFieldOrder(): List { + return listOf("data", "size", "id") + } +} + +interface LoadResourceFromOwner : Callback { + fun loadResourceFromOwner(resourceName: String?, owner: Pointer?): ResourceBuffer +} + +interface FreeResourceFromOwner : Callback { + fun freeResourceFromOwner(rb: ResourceBuffer, owner: Pointer?) +} +interface FilamentInterop : Library { + +fun create_swap_chain(viewer: Pointer, window:Pointer?, width:Int, height:Int); + +fun create_swap_chain_android(viewer: Pointer, surface:Object, + env:JNIEnv, width:Int, height:Int); +fun create_filament_viewer_android( + surface:Object, + env:JNIEnv, + resourceLoader:Pointer +) : Pointer; +fun delete_filament_viewer(viewer: Any?) +fun get_asset_manager(viewer: Any?): Any? +fun create_render_target(viewer: Any?, texture_id: Int, width: Int, height: Int) +fun clear_background_image(viewer: Any?) +fun set_background_image(viewer: Any?, path: String, fill_height: Boolean) +fun set_background_image_position(viewer: Any?, x: Float, y: Float, clamp: Boolean) +fun set_background_color(viewer: Any?, r: Float, g: Float, b: Float, a: Float) +fun set_tone_mapping(viewer: Any?, tone_mapping: Int) +fun set_bloom(viewer: Any?, strength: Float) +fun load_skybox(viewer: Any?, skybox_path: String) +fun load_ibl(viewer: Any?, ibl_path: String, intensity: Float) +fun remove_skybox(viewer: Any?) +fun remove_ibl(viewer: Any?) +fun add_light(viewer: Any?, type: Byte, colour: Float, intensity: Float, pos_x: Float, pos_y: Float, pos_z: Float, dir_x: Float, dir_y: Float, dir_z: Float, shadows: Boolean): EntityId +fun remove_light(viewer: Any?, entity_id: EntityId) +fun clear_lights(viewer: Any?) +fun load_glb(asset_manager: Any?, asset_path: String, unlit: Boolean): EntityId +fun load_gltf(asset_manager: Any?, asset_path: String, relative_path: String): EntityId +fun set_camera(viewer: Any?, asset: EntityId, node_name: String): Boolean +fun render(viewer: Any?, frame_time_in_nanos: Long) + +fun destroy_swap_chain(viewer: Any?) +fun set_frame_interval(viewer: Any?, interval: Float) +fun update_viewport_and_camera_projection(viewer: Any?, width: UInt, height: UInt, scale_factor: Float) +fun scroll_begin(viewer: Any?) +fun scroll_update(viewer: Any?, x: Float, y: Float, z: Float) +fun scroll_end(viewer: Any?) +fun grab_begin(viewer: Any?, x: Float, y: Float, pan: Boolean) +fun grab_update(viewer: Any?, x: Float, y: Float) +fun grab_end(viewer: Any?) +fun apply_weights(asset_manager: Any?, asset: EntityId, entity_name: String, weights: FloatArray, count: Int) +fun set_morph_target_weights(asset_manager: Any?, asset: EntityId, entity_name: String, morph_data: FloatArray, num_weights: Int) +fun set_morph_animation(asset_manager: Any?, asset: EntityId, entity_name: String, morph_data: FloatArray, morph_indices: IntArray, num_morph_targets: Int, num_frames: Int, frame_length_in_ms: Int): Boolean +fun set_bone_animation(asset_manager: Any?, asset: EntityId, frame_data: FloatArray, num_frames: Int, num_bones: Int, bone_names: Array, mesh_name: Array, num_mesh_targets: Int, frame_length_in_ms: Int) +fun play_animation(asset_manager: Any?, asset: EntityId, index: Int, loop: Boolean, reverse: Boolean, replace_active: Boolean, crossfade: Float) +fun set_animation_frame(asset_manager: Any?, asset: EntityId, animation_index: Int, animation_frame: Int) +fun stop_animation(asset_manager: Any?, asset: EntityId, index: Int) +fun get_animation_count(asset_manager: Any?, asset: EntityId): Int +fun get_animation_name(asset_manager: Any?, asset: EntityId, out_ptr: String, index: Int) +fun get_animation_duration(asset_manager: Any?, asset: EntityId, index: Int): Float +fun get_morph_target_name(asset_manager: Any?, asset: EntityId, mesh_name: String, out_ptr: String, index: Int) +fun get_morph_target_name_count(asset_manager: Any?, asset: EntityId, mesh_name: String): Int +fun remove_asset(viewer: Any?, asset: EntityId) +fun clear_assets(viewer: Any?) +fun load_texture(asset_manager: Any?, asset: EntityId, asset_path: String, renderable_index: Int) +fun set_texture(asset_manager: Any?, asset: EntityId) +fun set_material_color(asset_manager: Any?, asset: EntityId, mesh_name: String, material_index: Int, r: Float, g: Float, b: Float, a: Float): Boolean +fun transform_to_unit_cube(asset_manager: Any?, asset: EntityId) +fun set_position(asset_manager: Any?, asset: EntityId, x: Float, y: Float, z: Float) +fun set_rotation(asset_manager: Any?, asset: EntityId, rads: Float, x: Float, y: Float, z: Float) +fun set_scale(asset_manager: Any?, asset: EntityId, scale: Float) +fun set_camera_exposure(viewer: Any?, aperture: Float, shutter_speed: Float, sensitivity: Float) +fun set_camera_position(viewer: Any?, x: Float, y: Float, z: Float) +fun set_camera_rotation(viewer: Any?, rads: Float, x: Float, y: Float, z: Float) +fun set_camera_model_matrix(viewer: Any?, matrix: FloatArray) +fun set_camera_focal_length(viewer: Any?, focal_length: Float) +fun set_camera_focus_distance(viewer: Any?, focus_distance: Float) +fun hide_mesh(asset_manager: Any?, asset: EntityId, mesh_name: String): Int +fun reveal_mesh(asset_manager: Any?, asset: EntityId, mesh_name: String): Int +fun ios_dummy() +fun create_filament_viewer(context:Long, loader:Pointer) : Pointer; + +fun make_resource_loader(loadResourceFromOwner: LoadResourceFromOwner, freeResource: FreeResourceFromOwner, owner:Pointer?) : Pointer; } diff --git a/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt b/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt index be373c79..a5724ff4 100644 --- a/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt +++ b/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt @@ -1,90 +1,38 @@ package app.polyvox.filament + +import HotReloadPathHelper +import android.app.Activity +import android.content.res.AssetManager +import android.graphics.* +import android.opengl.* +import android.os.Build +import android.util.Log +import android.view.Choreographer +import android.view.Surface import androidx.annotation.NonNull - +import androidx.annotation.RequiresApi import androidx.lifecycle.Lifecycle - +import com.sun.jna.* +import io.flutter.FlutterInjector import io.flutter.embedding.engine.FlutterJNI +import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.plugin.common.BinaryMessenger +import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.embedding.engine.loader.FlutterApplicationInfo -import io.flutter.embedding.engine.loader.ApplicationInfoLoader - -import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference - -import android.content.res.AssetManager - -import android.Manifest -import android.app.Activity -import android.content.Context -import android.content.pm.PackageManager - -import io.flutter.FlutterInjector - -import android.os.CountDownTimer -import android.os.Handler - -import android.opengl.GLU -import javax.microedition.khronos.egl.EGLConfig -import javax.microedition.khronos.opengles.GL10 -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.nio.FloatBuffer -import android.hardware.Camera -import android.opengl.GLSurfaceView -import android.view.SurfaceView -import android.view.TextureView -import android.view.View -import android.view.Surface -import android.widget.TextView -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.lifecycle.DefaultLifecycleObserver - -import io.flutter.plugin.platform.PlatformView -import java.io.IOException - -import android.util.Log - -import com.sun.jna.Library -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.Memory -import com.sun.jna.ptr.PointerByReference -import com.sun.jna.ptr.IntByReference -import com.sun.jna.Structure -import com.sun.jna.NativeLibrary -import com.sun.jna.StringArray -import com.sun.jna.JNIEnv - -import android.R.attr.path -import android.graphics.* - -import java.util.Collections; - -import android.hardware.display.DisplayManager - -import com.google.android.filament.android.* -import com.google.android.filament.* - -import android.view.Choreographer -import android.view.Surface.CHANGE_FRAME_RATE_ALWAYS -import android.view.Surface.FRAME_RATE_COMPATIBILITY_DEFAULT -import android.view.SurfaceHolder - -import java.util.Timer -import java.util.concurrent.Executor +import io.flutter.view.TextureRegistry.SurfaceTextureEntry +import java.io.File +import java.util.* import java.util.concurrent.Executors +typealias EntityId = Int + /** PolyvoxFilamentPlugin */ -class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { +class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, LoadResourceFromOwner, FreeResourceFromOwner { private val lock = Object() @@ -92,15 +40,8 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private val startTime = System.nanoTime() override fun doFrame(frameTimeNanos: Long) { choreographer.postFrameCallback(this) - - executor.execute { - if(_viewer == null || !_render) { - - } else if(!surface.isValid()) { - Log.v(TAG, "INVALID") - } else { - _lib.render(_viewer!!, frameTimeNanos) - } + if(_viewer != null && _rendering) { + _lib.render(_viewer!!, frameTimeNanos) } } } @@ -110,13 +51,8 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { const val TAG = "FilamentPlugin" } - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity private lateinit var channel : MethodChannel - /// Keep a reference to the plugin binding so we can use the TextureRegistry when initialize is called from the platform channel. private lateinit var flutterPluginBinding : FlutterPlugin.FlutterPluginBinding private var lifecycle: Lifecycle? = null @@ -124,16 +60,20 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private lateinit var _lib : FilamentInterop private var _viewer : Pointer? = null - private var _render : Boolean = true + private var _rendering : Boolean = false + private var _glContext: EGLContext? = null + private var mEglDisplay: EGLDisplay? = null + private var _glTextureId = 0 + + private var _surfaceTexture: SurfaceTexture? = null + private var _surfaceTextureEntry: SurfaceTextureEntry? = null + private var _surface: Surface? = null private lateinit var choreographer: Choreographer private val frameCallback = FrameCallback() private lateinit var assetManager : AssetManager - - private lateinit var surface: Surface - private var surfaceTexture: SurfaceTexture? = null private lateinit var activity:Activity @@ -143,8 +83,7 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { this.flutterPluginBinding = flutterPluginBinding channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME) channel.setMethodCallHandler(this) - - _lib = Native.loadLibrary("filament_interop", FilamentInterop::class.java, Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true)) + _lib = Native.loadLibrary("polyvox_filament_android", FilamentInterop::class.java, Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true)) } override fun onAttachedToActivity(binding: ActivityPluginBinding) { @@ -155,412 +94,641 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { choreographer.postFrameCallback(frameCallback) } - fun getAssetPath(path:String) : String { - if(path.startsWith("file://")) { - return path - } - - val loader = FlutterInjector.instance().flutterLoader() - val key = loader.getLookupKeyForAsset(path) - val hotReloadPath = HotReloadPathHelper.getAssetPath(key, activity.getPackageName()) - if(hotReloadPath != null) { - return "file://" + hotReloadPath; - } - return key - } - + @RequiresApi(Build.VERSION_CODES.M) override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + Log.e("polyvox_filament", call.method, null) when (call.method) { - "initialize" -> { - val entry = flutterPluginBinding.textureRegistry.createSurfaceTexture(); - executor.execute { + "createTexture" -> { + if(_glTextureId != 0) { + result.success(_glTextureId) + return + } + if(_glContext == null) { + _glContext = EGL14.eglGetCurrentContext() + if (_glContext == EGL14.EGL_NO_CONTEXT) { - if(_viewer != null) { - _lib.delete_filament_viewer(_viewer!!); - _viewer = null; + // if(surfaceTextureEntry != null) { + // result.error("ERR", "Surface texture already exists. Call destroyTexture before creating a new one", null); + // } else { + // surface = Surface(surfaceTextureEntry!!.surfaceTexture()) + + // if(!surface!!.isValid) { + // result.error("ERR", "Surface creation failed. ", null); + // } else { + // result.success(surfaceTextureEntry!!.id().toInt()) + // } + // } + + mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY) + if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { + result.error("Err", "eglGetDisplay failed", null); + return; + } + + val version = IntArray(2) + + if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { + var error = EGL14.eglGetError(); + Log.e("DISPLAY_FAILED", "NativeEngine: failed to init display %d"); + } + + val attribs = intArrayOf( + EGL14.EGL_RENDERABLE_TYPE, EGL15.EGL_OPENGL_ES3_BIT, + EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_DEPTH_SIZE, 16, + EGL14.EGL_NONE); + var numConfigs = intArrayOf(1) + val configs: Array = arrayOf(null) + + if(!EGL14.eglChooseConfig(mEglDisplay, attribs, 0, configs, 0, 1, numConfigs, 0)) { + result.error("NO_GL_CONTEXT", "Failed to get matching EGLConfig", null); + return; + } + + _glContext = EGL14.eglCreateContext(mEglDisplay, configs[0]!!, EGL14.EGL_NO_CONTEXT, intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE), 0); + + if (_glContext === EGL14.EGL_NO_CONTEXT || EGL14.eglMakeCurrent(mEglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, _glContext) == false) { + result.error("NO_GL_CONTEXT", "Failed to get current OpenGL context", null); + return; + }; + Log.i("polyvox_filament", "Successfully created OpenGL context"); + } + } + + val args = call.arguments as List<*> + val width = 256 //args[0] as Double + val height = 256// args[1] as Double + if(width <1 || height < 1) { + result.error("DIMENSION_MISMATCH","Both dimensions must be greater than zero", null); + return; + } + Log.i("polyvox_filament", "Creating texture of size ${width}x${height}"); + val texture = IntArray(1) + GLES32.glGenTextures(1, texture, 0) + _glTextureId = texture[0] + + GLES32.glBindTexture(GLES32.GL_TEXTURE_2D, _glTextureId) + GLES32.glTexParameteri(GLES32.GL_TEXTURE_2D, GLES32.GL_TEXTURE_MIN_FILTER, GLES32.GL_LINEAR) + GLES32.glTexParameteri(GLES32.GL_TEXTURE_2D, GLES32.GL_TEXTURE_MAG_FILTER, GLES32.GL_LINEAR) + GLES32.glTexParameteri(GLES32.GL_TEXTURE_2D, GLES32.GL_TEXTURE_WRAP_S, GLES32.GL_CLAMP_TO_EDGE) + GLES32.glTexParameteri(GLES32.GL_TEXTURE_2D, GLES32.GL_TEXTURE_WRAP_T, GLES32.GL_CLAMP_TO_EDGE) + GLES32.glTexImage2D( + GLES32.GL_TEXTURE_2D, + 0, + GLES32.GL_RGBA, + width.toInt(), + height.toInt(), + 0, + GLES32.GL_RGBA, + GLES32.GL_UNSIGNED_BYTE, + null + ) + if(_glTextureId == 0) { + result.error("GL_TEXTURE_CREATE_FAILED", "Failed to create OpenGL texture. Check logcat for details", null); + return; + } + + _surfaceTexture = SurfaceTexture(_glTextureId) + _surfaceTexture!!.setDefaultBufferSize(width.toInt(), height.toInt()) + _surfaceTexture!!.setOnFrameAvailableListener({ + Log.i("TMP","FRAME AVAILABLE"); + }) + _surfaceTextureEntry = flutterPluginBinding.textureRegistry.registerSurfaceTexture(_surfaceTexture!!) + +// _surfaceTextureEntry = flutterPluginBinding.textureRegistry.createSurfaceTexture() +// _surfaceTexture = _surfaceTextureEntry!!.surfaceTexture(); + + _surface = Surface(_surfaceTexture) + val canvas = _surface!!.lockHardwareCanvas() + canvas.drawColor(Color.GREEN) + _surface!!.unlockCanvasAndPost(canvas) +// result.success(_surfaceTextureEntry!!.id()) + result.success(0) + } + "destroyTexture" -> { + if (_viewer != null) { + result.error("INVALID_ARGUMENTS", "Destroy the viewer before destroying the texture", null) + } else { + _surface!!.release(); + _surfaceTextureEntry!!.release(); + _surface = null + _surfaceTextureEntry = null } - if(surfaceTexture != null) { - surfaceTexture!!.release() - surfaceTexture = null; - } - val args = call.arguments as ArrayList - val width = args[0] - val height = args[1] - - surfaceTexture = entry.surfaceTexture() - - surfaceTexture!!.setDefaultBufferSize(width, height) - - surface = Surface(surfaceTexture!!) - - _viewer = _lib.create_filament_viewer_android( - surface as Object, - JNIEnv.CURRENT, - (activity as Context).assets) - _lib.update_viewport_and_camera_projection(_viewer!!, width, height, 1.0f); - - result.success(entry.id().toInt()) + } + "destroyViewer" -> { + if (_viewer != null) { + _lib.destroy_swap_chain(_viewer) + _lib.delete_filament_viewer(_viewer) + _viewer = null + } + result.success(true) + } + "resize" -> { + result.success(null) + return + + if (_viewer == null) { + result.error("VIEWER_NULL", "Error: cannot resize before a viewer has been created", null) + return + } + _rendering = false + _lib.destroy_swap_chain(_viewer) + val args = call.arguments as List + val width = args[0] as Int + val height = args[1] as Int + val scale = args[2] as Float + _surfaceTexture!!.setDefaultBufferSize(width, height) + // _lib.create_swap_chain(Pointer.nativeValue(_viewer), Surface(_surfaceTexture), width, height) + _lib.update_viewport_and_camera_projection(_viewer!!, width as UInt, height as UInt, scale); + _rendering = true + Log.i(TAG, "Resized to ${args[0]}x${args[1]}") + result.success(_surfaceTexture) + } + "createFilamentViewer" -> { + if (_viewer != null) { + _lib.destroy_swap_chain(_viewer) + _lib.delete_filament_viewer(_viewer) + _viewer = null + } + val resourceLoader = _lib.make_resource_loader(this, this, Pointer(0)) +// _viewer = _lib.create_filament_viewer(0, resourceLoader); +// _viewer = _lib.create_filament_viewer_android(_surface!! as Object, JNIEnv.CURRENT, resourceLoader); +//surface. + _viewer = _lib.create_filament_viewer( + _glContext!!.nativeHandle, + resourceLoader) + val args = call.arguments as List +// val width = args[0] as Double +// val height = args[1] as Double + val width = 200 + val height = 200 +// _lib.create_swap_chain_android(_viewer!!, _surface!! as Object, JNIEnv.CURRENT, width.toInt(), height.toInt()) + _lib.create_swap_chain(_viewer!!, null, width.toInt(), height.toInt()) + _lib.create_render_target(_viewer, _glTextureId, width.toInt(),height.toInt()); + result.success(Pointer.nativeValue(_viewer!!)) } + "getAssetManager" -> { + val assetManager = _lib.get_asset_manager(_viewer) + result.success(assetManager) } - "resize" -> { - executor.execute { - val args = call.arguments as ArrayList - val width = args[0] - val height = args[1] - val scale = if(args.size > 2) (args[2] as Double).toFloat() else 1.0f - surfaceTexture!!.setDefaultBufferSize(width, height) - _lib.update_viewport_and_camera_projection(_viewer!!, width, height, scale); - result.success(null) - } - } - "render" -> { - executor.execute { - _lib.render(_viewer!!, 0) - result.success(null) - } - } - "setRendering" -> { - _render = call.arguments as Boolean - Log.v(TAG, "Set rendering to ${_render}") - result.success(null) - } - "setFrameInterval" -> { - executor.execute { - _lib.set_frame_interval(_viewer!!, (call.arguments as Double).toFloat()); - result.success(null) - } + "clearBackgroundImage" -> { + _lib.clear_background_image(_viewer) + result.success(true) } "setBackgroundImage" -> { - executor.execute { - _lib.set_background_image(_viewer!!, getAssetPath(call.arguments as String)) - result.success("OK"); - } + val args = call.arguments as List<*> + + val path = args[0] as String + val fillHeight = args[1] as Boolean + _lib.set_background_image(_viewer, path, fillHeight) + result.success(true) } "setBackgroundImagePosition" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.set_background_image_position(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat(), (args[2] as Boolean)) - result.success("OK"); - } + val args = call.arguments as List<*> + _lib.set_background_image_position(_viewer, args[0] as Float, args[1] as Float, args[2] as Boolean) + result.success(true) + } + "setBackgroundColor" -> { + val args = call.arguments as List + _lib.set_background_color(_viewer, args[0].toFloat(), args[1].toFloat(), args[2].toFloat(), args[3].toFloat()) + result.success(true) + } + "setToneMapping" -> { + val args = call.arguments as Int + _lib.set_tone_mapping(_viewer, args); + result.success(true) + } + "setBloom" -> { + val args = call.arguments as Float + _lib.set_bloom(_viewer, args as Float); + result.success(true) } "loadSkybox" -> { - executor.execute { - _lib.load_skybox(_viewer!!, getAssetPath(call.arguments as String)) - result.success("OK"); - } + _lib.load_skybox(_viewer, call.arguments as String) + result.success(true) } "loadIbl" -> { - executor.execute { - _lib.load_ibl(_viewer!!, getAssetPath(call.arguments as String)) - result.success("OK"); - } - } - "removeIbl" -> { - executor.execute { - _lib.remove_ibl(_viewer!!) - result.success(true); - } + val args = call.arguments as List<*> + _lib.load_ibl(_viewer, args[0] as String, args[1] as Float) + result.success(true) } "removeSkybox" -> { - executor.execute { - _lib.remove_skybox(_viewer!!) - result.success(true); - } + _lib.remove_skybox(_viewer) + result.success(true) + } + "removeIbl" -> { + _lib.remove_ibl(_viewer) + result.success(true) } "addLight" -> { - executor.execute { - val args = call.arguments as ArrayList - val entity = _lib.add_light( - _viewer!!, - args[0] as Int, - (args[1] as Double).toFloat(), - (args[2] as Double).toFloat(), - (args[3] as Double).toFloat(), - (args[4] as Double).toFloat(), - (args[5] as Double).toFloat(), - (args[6] as Double).toFloat(), - (args[7] as Double).toFloat(), - (args[8] as Double).toFloat(), - (args[9] as Boolean)) - result.success(entity); - } + val args = call.arguments as? List<*> + if (args != null && args.size == 10) { + val type = (args[0] as Int)?.toByte() + val colour = args[1] as Float + val intensity = args[2] as Float + val posX = args[3] as Float + val posY = args[4] as Float + val posZ = args[5] as Float + val dirX = args[6] as Float + val dirY = args[7] as Float + val dirZ = args[8] as Float + val shadows = args[9] as Boolean + + val entityId = _lib.add_light(_viewer, type, colour as Float, intensity as Float,posX as Float, posY as Float, posZ as Float, dirX as Float, dirY as Float, dirZ as Float, shadows) + result.success(entityId) + } } "removeLight" -> { - executor.execute { - _lib.remove_light( - _viewer!!, - call.arguments as Int) - result.success(true); + _lib.remove_light(_viewer, call.arguments as Int) + result.success(true) + } + "clearLights" -> { + _lib.clear_lights(_viewer) + result.success(true) + } + "loadGlb" -> { + val args = call.arguments as List + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected assetManager, assetPath, and unlit for load_glb", null) + return } - } - "clearLights" -> { - executor.execute { - _lib.clear_lights( - _viewer!! - ) - result.success(true); + val assetManager = args[0] as Int + val assetPath = args[1] as String + val unlit = args[2] as Boolean + val entityId =_lib.load_glb(assetManager, assetPath, unlit) + result.success(entityId) + } + "loadGltf" -> { + val args = call.arguments as List + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected assetManager, assetPath, and relativePath for load_gltf", null) + return } - } - "loadGlb" -> { - executor.execute { - val assetPtr = _lib.load_glb( - _viewer!!, - getAssetPath(call.arguments as String) - ) - result.success(Pointer.nativeValue(assetPtr)); + val assetManager = args[0] as Int + val assetPath = args[1] as String + val relativePath = args[2] as String + val entityId =_lib.load_gltf(assetManager, assetPath, relativePath) + result.success(entityId) + } + "transformToUnitCube" -> { + val args = call.arguments as List + _lib.transform_to_unit_cube(args[0] as Int, args[1] as EntityId) + result.success(true) + } + "render" -> { + val canvas = _surface!!.lockHardwareCanvas() + canvas.drawColor(Color.BLUE) + _surface!!.unlockCanvasAndPost(canvas) + _lib.render(_viewer, 0) +// _surfaceTexture!!.updateTexImage() + result.success(true) + } + "setRendering" -> { + _rendering = call.arguments as Boolean + result.success(true) + } + "setFrameInterval" -> { + if (_viewer != null) { + _lib.set_frame_interval(_viewer, call.arguments as Float) } - } - "loadGltf" -> { - executor.execute { - val args = call.arguments as ArrayList - val assetPtr = _lib.load_gltf( - _viewer!!, - getAssetPath(args[0] as String), - getAssetPath(args[1] as String) - ) - result.success(Pointer.nativeValue(assetPtr)); - } - } - "transformToUnitCube" -> { - executor.execute { - val assetPtr = Pointer(call.arguments as Long); - _lib.transform_to_unit_cube(assetPtr) - result.success("OK"); - } - } - "setPosition" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - val assetPtr = Pointer(args[0] as Long) - _lib.set_position(assetPtr, (args[1] as Double).toFloat(), (args[2] as Double).toFloat(), (args[3] as Double).toFloat()) - result.success("OK"); - } - } - "setScale" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - val assetPtr = Pointer(args[0] as Long) - _lib.set_scale(assetPtr, (args[1] as Double).toFloat()) - result.success("OK"); - } - } - "setRotation" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - val assetPtr = Pointer(args[0] as Long) - _lib.set_rotation(assetPtr, (args[1] as Double).toFloat(), (args[2] as Double).toFloat(), (args[3] as Double).toFloat(), (args[4] as Double).toFloat()) - result.success("OK"); - } - } - "setCameraPosition" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - _lib.set_camera_position(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat(), (args[2] as Double).toFloat()) - result.success("OK"); - } - } - "setCameraRotation" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - _lib.set_camera_rotation(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat(), (args[2] as Double).toFloat(), (args[3] as Double).toFloat()) - result.success("OK"); - } - } - "setCameraFocalLength" -> { - executor.execute { - _lib.set_camera_focal_length(_viewer!!, (call.arguments as Double).toFloat()) - result.success("OK"); - } - } - "setCameraFocusDistance" -> { - executor.execute { - _lib.set_camera_focus_distance(_viewer!!, (call.arguments as Double).toFloat()) - result.success("OK"); - } - } - "setTexture" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - val assetPtr = Pointer(args[0] as Long); - _lib.load_texture(assetPtr, getAssetPath(args[1] as String), args[2] as Int) - print("Texture loaded") - result.success("OK"); - } - - } - "setCamera" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - val success = _lib.set_camera( - _viewer!!, - Pointer(args[0] as Long), - args[1] as String, - ) - if(success) { - result.success("OK"); - } else { - result.error("failed","failed", "Failed to set camera") + result.success(true) + } + "updateViewportAndCameraProjection" -> { + val args = call.arguments as List + val width = args[0] as Int + val height = args[1] as Int + val scaleFactor = args[2] as Double + _lib.update_viewport_and_camera_projection(_viewer, width.toUInt(), height.toUInt(), scaleFactor.toFloat()) + result.success(true) + } + "scrollBegin" -> { + _lib.scroll_begin(_viewer) + result.success(true) + } + "scrollUpdate" -> { + val args = call.arguments as List + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected viewer, x, y, and z for scroll_update", null) + return } - } + val x = args[0] as Float + val y = args[1] as Float + val z = args[2] as Float + _lib.scroll_update(_viewer, x.toFloat(), y.toFloat(), z.toFloat()) + result.success(true) } - "zoomBegin" -> { - executor.execute { - _lib.scroll_begin(_viewer!!) - result.success("OK"); - } + "scrollEnd" -> { + _lib.scroll_end(_viewer) + result.success(true) } - "zoomUpdate" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - _lib.scroll_update(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat(), (args[2] as Double).toFloat()) - result.success("OK"); - } - } - "zoomEnd" -> { - executor.execute { - _lib.scroll_end(_viewer!!) - result.success("OK"); - } - } - "getMorphTargetNames" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - val assetPtr = Pointer(args[0] as Long) - val meshName = args[1] as String - val names = mutableListOf() - val outPtr = Memory(256) - for(i in 0.._lib.get_morph_target_name_count(assetPtr, meshName) - 1) { - _lib.get_morph_target_name(assetPtr, meshName, outPtr, i) - val name = outPtr.getString(0) - names.add(name) + "grabBegin" -> { + val args = call.arguments as List + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected viewer, x, y, and pan for grab_begin", null) + return } - result.success(names) - } - } - "getAnimationNames" -> { - executor.execute { - val assetPtr = Pointer(call.arguments as Long) - val names = mutableListOf() - val outPtr = Memory(256) - for(i in 0.._lib.get_animation_count(assetPtr) - 1) { - _lib.get_animation_name(assetPtr, outPtr, i) - val name = outPtr.getString(0) - names.add(name) - } - result.success(names) - } - } - "setMorphTargetWeights" -> { - executor.execute { - val args = call.arguments as ArrayList<*> - val assetPtr = Pointer(args[0] as Long) - val weights = args[1] as ArrayList; - - _lib.apply_weights(assetPtr, weights.toFloatArray(), weights.size) - result.success("OK"); - } - } - "setAnimation" -> { - executor.execute { - val args = call.arguments as ArrayList - val assetPtr = Pointer(args[0] as Long) - val frames = args[1] as ArrayList; - val numWeights = args[2] as Int - val numFrames = args[3] as Int - val frameLenInMs = args[4] as Double - - _lib.set_animation(assetPtr, frames.toFloatArray(), numWeights, numFrames, frameLenInMs.toFloat()) - result.success("OK"); - } - } - "panStart" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.grab_begin(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat(), true) - result.success("OK"); - } - } - "panUpdate" -> { - executor.execute { - val args = call.arguments as ArrayList - val x = (args[0] as Double).toFloat() - val y = (args[1] as Double).toFloat() - _lib.grab_update(_viewer!!, x, y) - result.success("OK"); - } - } - "panEnd" -> { - executor.execute { - _lib.grab_end(_viewer!!) - result.success("OK"); - } - } - "rotateStart" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.grab_begin(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat(), false) - result.success("OK"); - } - } - "rotateUpdate" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.grab_update(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat()) - result.success("OK"); - } - } - "rotateEnd" -> { - executor.execute { - _lib.grab_end(_viewer!!) - result.success("OK"); - } - } - "grabStart" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.grab_begin(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat(), true) - result.success("OK"); - } + val x = args[0] as Float + val y = args[1] as Float + val pan = args[2] as Boolean + _lib.grab_begin(_viewer, x.toFloat(), y.toFloat(), pan) + result.success(true) } "grabUpdate" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.grab_update(_viewer!!, (args[0] as Double).toFloat(), (args[1] as Double).toFloat()) - result.success("OK"); - } + val args = call.arguments as List + if (args.size != 2) { + result.error("INVALID_ARGUMENTS", "Expected viewer, x, y for grab_update", null) + return + } + val x = args[0] as Float + val y = args[1] as Float + _lib.grab_update(_viewer, x.toFloat(), y.toFloat()) + result.success(true) } "grabEnd" -> { - executor.execute { - _lib.grab_end(_viewer!!) - result.success("OK"); - } + _lib.grab_end(_viewer) + result.success(true) } - "removeAsset" -> { - executor.execute { - _lib.remove_asset(_viewer!!, Pointer(call.arguments as Long)) - result.success("OK"); + "applyWeights" -> { + // Use the following code snippet to get the arguments from call.arguments + // guard let args = call.arguments as? List<*>, args.count == 5, + // let assetManager = args[0] as? Int64, + // let asset = args[1] as? EntityId, + // let entityName = args[2] as? String, + // let weights = args[3] as? [Float], + // let count = args[4] as? Int else { + // result.success(FlutterError(code: "INVALID_ARGUMENTS", message: "Expected correct arguments for apply_weights", details: nil)) + // return + // } + // _lib.apply_weights(assetManager, asset, entityName, UnsafeMutablePointer(&weights), count) + result.success(true) + } + "setMorphTargetWeights" -> { + val args = call.arguments as List + val assetManager = args[0] as Int + val asset = args[1] as Int + val entityName = args[2] as String + val morphData = args[3] as List + val numMorphWeights = args[4] as Int + + _lib.set_morph_target_weights( + assetManager, + asset, + entityName, + morphData.map { it.toFloat() }.toFloatArray(), + numMorphWeights + ) + + result.success(true) + } + "setMorphAnimation" -> { + val args = call.arguments as List + val assetManager = args[0] as Int + val asset = args[1] as Int + val entityName = args[2] as String + val morphData = args[3] as List + val morphIndices = args[4] as List + val numMorphTargets = args[5] as Int + val numFrames = args[6] as Int + val frameLengthInMs = args[7] as Int + + val frameData = morphData.map { it.toFloat() } + + val success =_lib.set_morph_animation( + assetManager, + asset, + entityName, + frameData.toFloatArray(), + morphIndices.toIntArray(), + numMorphTargets, + numFrames, + frameLengthInMs + ) + result.success(success) + } + "setBoneAnimation" -> { + val args = call.arguments as Array<*> + if (args.size != 9) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for set_bone_animation", null) + return } - } - "clearAssets" -> { - executor.execute { - _lib.clear_assets(_viewer!!) - result.success("OK"); - } - } + val assetManager = args[0] as Int + val asset = args[1] as Int + val frameData = args[2] as FloatArray + val numFrames = args[3] as Int + val numBones = args[4] as Int + val boneNames = args[5] as Array + val meshNames = args[6] as Array + val numMeshTargets = args[7] as Int + val frameLengthInMs = args[8] as Int + _lib.set_bone_animation(assetManager, asset, frameData, numFrames, numBones, boneNames, meshNames, numMeshTargets, frameLengthInMs) + result.success(true) + } "playAnimation" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.play_animation(Pointer(args[0] as Long), args[1] as Int, args[2] as Boolean, args[3] as Boolean) - result.success("OK") + val args = call.arguments as Array<*> + if (args.size != 7) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for play_animation", null) + return } + val assetManager = args[0] as Int + val asset = args[1] as Int + val index = args[2] as Int + val loop = args[3] as Boolean + val reverse = args[4] as Boolean + val replaceActive = args[5] as Boolean + val crossfade = args[6] as Float + _lib.play_animation(assetManager, asset, index, loop, reverse, replaceActive, crossfade.toFloat()) + result.success(true) + } + "getAnimationDuration" -> { + val args = call.arguments as List<*> + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for getAnimationDuration", null) + return + } + val assetManager = args[0] as Int + val asset = args[1] as Int + val animationIndex = args[2] as Int + + val dur =_lib.get_animation_duration(assetManager, asset, animationIndex) + result.success(dur) + } + "setAnimationFrame" -> { + val args = call.arguments as List<*> + if (args.size != 4) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for set_animation_frame", null) + return + } + val assetManager = args[0] as Int + val asset = args[1] as Int + val animationIndex = args[2] as Int + val animationFrame = args[3] as Int + + _lib.set_animation_frame(assetManager, asset, animationIndex, animationFrame) + result.success(true) } "stopAnimation" -> { - executor.execute { - val args = call.arguments as ArrayList - _lib.stop_animation(Pointer(args[0] as Long), args[1] as Int) - result.success("OK") + val args = call.arguments as List<*> + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for stop_animation", null) + return } + val assetManager = args[0] as Int + val asset = args[1] as Int + val index = args[2] as Int + _lib.stop_animation(assetManager, asset, index) + result.success(true) + } + "getAnimationCount" -> { + val args = call.arguments as List<*> + if (args.size != 2) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for get_animation_count", null) + return + } + val assetManager = args[0] as Int + val asset = args[1] as Int + val count =_lib.get_animation_count(assetManager, asset) + result.success(count) + } + "getAnimationNames" -> { + val args = call.arguments as List<*> + if (args.size != 2) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for get_animation_name", null) + return + } + val assetManager = args[0] as Int + val asset = args[1] as Int + val names = mutableListOf() + val count =_lib.get_animation_count(assetManager, asset) + val buffer = "" // Assuming max name length of 256 for simplicity + for (i in 0 until count) { + _lib.get_animation_name(assetManager, asset, buffer, i) + names.add(buffer) + } + result.success(names) + } + "getAnimationName" -> { + val args = call.arguments as List<*> + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for get_animation_name", null) + return + } + val assetManager = args[0] as Int + val asset = args[1] as Int + val index = args[2] as Int + val buffer = "" // Assuming max name length of 256 for simplicity + _lib.get_animation_name(assetManager, asset, buffer, index) + val name = buffer + result.success(name) + } + "getMorphTargetName" -> { + val args = call.arguments as? List<*> + if (args == null || args.size != 4) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for get_morph_target_name", null) + return + } + val assetManager = args[0] as? Long ?: return + val asset = args[1] as? EntityId ?: return + val meshName = args[2] as? String ?: return + val index = args[3] as? Int ?: return + + val buffer = "" // Assuming max name length of 256 for simplicity + _lib.get_morph_target_name(assetManager, asset, meshName, buffer, index) + val targetName = buffer + result.success(targetName) + } + "getMorphTargetNames" -> { + val args = call.arguments as? List<*> + if (args == null || args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for get_morph_target_names", null) + return + } + val assetManager = args[0] as? Long ?: return + val asset = args[1] as? EntityId ?: return + val meshName = args[2] as? String ?: return + + val count =_lib.get_morph_target_name_count(assetManager, asset, meshName) + val names = ArrayList() + if (count > 0) { + for (i in 0 until count) { + val buffer = "" // Assuming max name length of 256 for simplicity + _lib.get_morph_target_name(assetManager, asset, meshName, buffer, i) + names.add(buffer) + } + } + result.success(names) + } + "getMorphTargetNameCount" -> { + val args = call.arguments as List<*> + if (args.size != 3) { + result.error("INVALID_ARGUMENTS", "Expected correct arguments for getMorphTargetNameCount", null) + return + } + val assetManager = args[0] as Int + val asset = args[1] as Int + val meshName = args[2] as String + val count =_lib.get_morph_target_name_count(assetManager, asset, meshName) + result.success(count) + } + "removeAsset" -> { + _lib.remove_asset(_viewer, call.arguments as Int) + result.success(true) + } + "clearAssets" -> { + _lib.clear_assets(_viewer) + result.success(true) + } + "setCamera" -> { + val args = call.arguments as List<*> + if (args.size != 2) { + result.error("INVALID_ARGUMENTS", "Expected asset and nodeName for setCamera", null) + return + } + val asset = args[0] as Int + val nodeName = args[1] as String + val success =_lib.set_camera(_viewer, asset, nodeName) + result.success(success) + } + "setCameraPosition" -> { + val args = call.arguments as List<*> + _lib.set_camera_position(_viewer, (args[0] as Float).toFloat(), (args[1] as Float).toFloat(), (args[2] as Float).toFloat()) + result.success(true) + } + "setCameraRotation" -> { + val args = call.arguments as List<*> + _lib.set_camera_rotation(_viewer, args[0] as Float, args[1] as Float, args[2] as Float, args[3] as Float) + result.success(true) + } + "setCameraModelMatrix" -> { + val matrix = call.arguments as List + _lib.set_camera_model_matrix(_viewer, matrix.toFloatArray()) + result.success(true) + } + "setCameraFocalLength" -> { + _lib.set_camera_focal_length(_viewer, call.arguments as Float) + result.success(true) + } + "setCameraFocusDistance" -> { + _lib.set_camera_focus_distance(_viewer, call.arguments as Float) + result.success(true) + } + "setMaterialColor" -> { + val args = call.arguments as List<*> + _lib.set_material_color(args[0] as Int, args[1] as Int, args[2] as String, args[3] as Int, args[4] as Float, args[5] as Float, args[6] as Float, args[7] as Float) + result.success(true) + } + "hideMesh" -> { + val args = call.arguments as List<*> + val status =_lib.hide_mesh(args[0] as Int, args[1] as Int, args[2] as String) + result.success(status) + } + "revealMesh" -> { + val args = call.arguments as List<*> + val status =_lib.reveal_mesh(args[0] as Int, args[1] as Int, args[2] as String) + result.success(status) } else -> { result.notImplemented() @@ -570,13 +738,11 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) - _lib.destroy_swap_chain(_viewer!!) } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { onAttachedToActivity(binding) - _lib.create_swap_chain(_viewer!!, surface, JNIEnv.CURRENT) } override fun onDetachedFromActivityForConfigChanges() { @@ -586,4 +752,56 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { override fun onDetachedFromActivity() { lifecycle = null } + + val _resources:MutableMap = mutableMapOf(); + var _lastId = 1 + var _ptr:Pointer = Pointer(0) + + override fun loadResourceFromOwner(path: String?, owner: Pointer?): ResourceBuffer { + var data:ByteArray? = null + if(path!!.startsWith("file://")) { + data = File(path!!.substring(6)).readBytes() + } else { + val loader = FlutterInjector.instance().flutterLoader() + val key = loader.getLookupKeyForAsset(path) + val hotReloadPath = HotReloadPathHelper.getAssetPath(key, activity.getPackageName()) + if (hotReloadPath != null) { + data = File(hotReloadPath).readBytes() + } else { + val assetManager: AssetManager = activity.assets + try { + data = assetManager.open(key).readBytes() + } catch (e:Exception) { + Log.e("polyvox_filament", "Failed to open asset at ${path}", null) + } + } + } + val rb = ResourceBuffer(); + try { + if (data != null) { + val dataPtr = Memory(data.size.toLong()) + dataPtr.write(0, data, 0, data.size) + rb.data = dataPtr + rb.size = data.size + rb.id = _lastId + _resources[rb.id] = dataPtr; + _lastId++ + } else { + rb.id = 0 + rb.size = 0 + rb.data = Pointer(0) + } + } catch(e:Exception) { + Log.e("polyvox_filament", "Error setting resource buffer : $e", null); + } + rb.write(); + return rb; + + } + + override fun freeResourceFromOwner(rb: ResourceBuffer, owner: Pointer?) { + _resources.remove(rb.id) + } + + }