manual OpenGL texture instantiation on Android
This commit is contained in:
26
android/src/main/cpp/FilamentAndroid.cpp
Normal file
26
android/src/main/cpp/FilamentAndroid.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <android/native_window_jni.h>
|
||||
#include <android/native_activity.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <utils/JobSystem.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/memalign.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#if !defined(WIN32)
|
||||
# include <pthread.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
# include <sys/time.h>
|
||||
# include <sys/resource.h>
|
||||
# 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 <unistd.h>
|
||||
# include <sys/syscall.h>
|
||||
# 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<Job *>(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<ThreadState>(threadPoolCount + adoptableThreadsCount);
|
||||
mThreadCount = uint16_t(threadPoolCount);
|
||||
mParallelSplitCount = (uint8_t)std::ceil((std::log2f(threadPoolCount + adoptableThreadsCount)));
|
||||
|
||||
static_assert(std::atomic<bool>::is_always_lock_free);
|
||||
static_assert(std::atomic<uint16_t>::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<Mutex> 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<Mutex>& 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<Mutex> 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<Mutex> 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<utils::SpinLock> 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<Job>();
|
||||
}
|
||||
|
||||
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<Mutex> 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<Mutex> 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<utils::SpinLock> 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<utils::SpinLock> 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
|
||||
@@ -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 <gltfio/TextureProvider.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/Texture.h>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
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<intptr_t> decodedTexelsBaseMipmap;
|
||||
vector<uint8_t> 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<unique_ptr<TextureInfo> > 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
|
||||
@@ -1,90 +0,0 @@
|
||||
#include "FilamentViewer.hpp"
|
||||
#include "SceneAsset.hpp"
|
||||
#include "ResourceBuffer.hpp"
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <android/log.h>
|
||||
#include <android/native_activity.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
using namespace polyvox;
|
||||
using namespace std;
|
||||
|
||||
static AAssetManager* am;
|
||||
static map<uint32_t, AAsset*> _apk_assets;
|
||||
static map<uint32_t, void*> _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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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 <code>SwapChain</code> represents an Operating System's <b>native</b> renderable surface.
|
||||
*
|
||||
* <p>Typically it's a native window or a view. Because a <code>SwapChain</code> is initialized
|
||||
* from a native object, it is given to filament as an <code>Object</code>, which must be of the
|
||||
* proper type for each platform filament is running on.</p>
|
||||
*
|
||||
* <code>
|
||||
* SwapChain swapChain = engine.createSwapChain(nativeWindow);
|
||||
* </code>
|
||||
*
|
||||
* <p>The <code>nativeWindow</code> parameter above must be of type:</p>
|
||||
*
|
||||
* <center>
|
||||
* <table border="1">
|
||||
* <tr><th> Platform </th><th> nativeWindow type </th></tr>
|
||||
* <tr><td> Android </td><td>{@link android.view.Surface Surface}</td></tr>
|
||||
* </table>
|
||||
* </center>
|
||||
* <p>
|
||||
*
|
||||
* <h1>Examples</h1>
|
||||
*
|
||||
* <h2>Android</h2>
|
||||
*
|
||||
*
|
||||
* <p>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()}.</p>
|
||||
*
|
||||
* <p>To use a {@link android.view.TextureView Textureview} as a <code>SwapChain</code>, 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}:</p>
|
||||
*
|
||||
* <pre>
|
||||
* // 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()
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @see Engine
|
||||
*/
|
||||
public class SwapChain {
|
||||
private final Object mSurface;
|
||||
private long mNativeObject;
|
||||
|
||||
public static final long CONFIG_DEFAULT = 0x0;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> must be allocated with an
|
||||
* alpha-channel.
|
||||
*/
|
||||
public static final long CONFIG_TRANSPARENT = 0x1;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> may be used as a source surface
|
||||
* for reading back render results. This config must be set when creating
|
||||
* any <code>SwapChain</code> 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 <code>Object</code> this <code>SwapChain</code> 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.
|
||||
*
|
||||
* <p>
|
||||
* Use setFrameCompletedCallback to set a callback on an individual SwapChain. Each time a frame
|
||||
* completes GPU rendering, the callback will be called.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The FrameCompletedCallback is guaranteed to be called on the main Filament thread.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the
|
||||
* callback (which will never be called) and proceed normally.
|
||||
* </p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
@@ -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());
|
||||
// }
|
||||
// }
|
||||
@@ -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:
|
||||
*
|
||||
* <pre>
|
||||
* 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();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
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 <code>isReadyToRender()</code>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> {
|
||||
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<String>, mesh_name: Array<String>, 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;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user