gltfio: Allow Java / Kotlin clients to customize MaterialProvider.

This adds some significant JNI glue in order to make the Java API for
gltfio materials much more consistent with C++, and create feature
parity between the two languages.

The motivation is to allow Java clients to provide their own
ubershader-based solution or filamat-based solution.

- The `MaterialProvider` Java class is now an interface.
- The `UbershaderLoader` Java class is now a concrete implementation.
- Users can now write their own provider class using Java / Kotlin.

To streamline the amount of JNI work that occurs at run time, the
MaterialProvider Java interface uses ints in a couple spots instead
of enums. This sacrafices some readability for Java clients who wish
to write their own provider, but makes the JNI much simpler.

This replaces #3975.
This commit is contained in:
Philip Rideout
2021-07-01 16:11:55 -07:00
parent f4558fee1d
commit d270c42109
14 changed files with 556 additions and 93 deletions
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* Copyright (C) 2021 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.
@@ -23,18 +23,151 @@
#include <gltfio/AssetLoader.h>
#include <gltfio/MaterialProvider.h>
#include <utils/debug.h>
#include "common/NioUtils.h"
#include "MaterialKey.h"
using namespace filament;
using namespace gltfio;
using namespace utils;
class JavaMaterialProvider : public MaterialProvider {
JNIEnv* const mEnv;
const jobject mJavaProvider;
mutable Material** mPreviousMaterials = nullptr;
jclass mMaterialKeyClass;
jmethodID mMaterialKeyConstructor;
jmethodID mCreateMaterialInstance;
jmethodID mGetMaterials;
jmethodID mNeedsDummyData;
jmethodID mDestroyMaterials;
jmethodID mMaterialInstanceGetNativeObject;
jmethodID mMaterialGetNativeObject;
public:
JavaMaterialProvider(JNIEnv* env, jobject provider) : mEnv(env),
mJavaProvider(env->NewGlobalRef(provider)) {
mMaterialKeyClass = env->FindClass(JAVA_MATERIAL_KEY);
mMaterialKeyClass = (jclass) env->NewGlobalRef(mMaterialKeyClass);
assert_invariant(mMaterialKeyClass);
mMaterialKeyConstructor = env->GetMethodID(mMaterialKeyClass, "<init>", "()V");
assert_invariant(mMaterialKeyConstructor);
jclass materialInstanceClass = env->FindClass("com/google/android/filament/MaterialInstance");
mMaterialInstanceGetNativeObject = env->GetMethodID(materialInstanceClass, "getNativeObject", "()J");
assert_invariant(mMaterialInstanceGetNativeObject);
jclass materialClass = env->FindClass("com/google/android/filament/Material");
mMaterialGetNativeObject = env->GetMethodID(materialClass, "getNativeObject", "()J");
assert_invariant(mMaterialGetNativeObject);
jclass providerClass = env->GetObjectClass(provider);
mCreateMaterialInstance = env->GetMethodID(providerClass, "createMaterialInstance",
"(L" JAVA_MATERIAL_KEY ";[ILjava/lang/String;)Lcom/google/android/filament/MaterialInstance;");
assert_invariant(mCreateMaterialInstance);
mGetMaterials = env->GetMethodID(providerClass, "getMaterials",
"()[Lcom/google/android/filament/Material;");
assert_invariant(mGetMaterials);
mNeedsDummyData = env->GetMethodID(providerClass, "needsDummyData","(I)Z");
assert_invariant(mNeedsDummyData);
mDestroyMaterials = env->GetMethodID(providerClass, "destroyMaterials", "()V");
assert_invariant(mDestroyMaterials);
}
~JavaMaterialProvider() override {
mEnv->DeleteGlobalRef(mMaterialKeyClass);
mEnv->DeleteGlobalRef(mJavaProvider);
delete mPreviousMaterials;
}
MaterialInstance* createMaterialInstance(MaterialKey* config, UvMap* uvmap, const char* label) override {
// Create a Java object for the material key and copy the native fields into it.
jobject key = mEnv->NewObject(mMaterialKeyClass, mMaterialKeyConstructor);
nativeToJava(mEnv, *config, key);
// Convert the optional label into a Java string.
jstring stringLabel = label ? mEnv->NewStringUTF(label) : nullptr;
// Allocate space for the output argument.
jintArray uvMapArray = mEnv->NewIntArray(8);
// Call the Java-based material provider.
jobject materialInstance = mEnv->CallObjectMethod(mJavaProvider, mCreateMaterialInstance,
key, uvMapArray, stringLabel);
// Copy the UvMap results from the JVM array into the native array.
if (uvmap) {
jint* elements = mEnv->GetIntArrayElements(uvMapArray, nullptr);
for (size_t i = 0; i < uvmap->size(); i++) {
(*uvmap)[i] = (UvSet) elements[i];
}
mEnv->ReleaseIntArrayElements(uvMapArray, elements, JNI_ABORT);
}
// The config parameter is an in-out parameter so we need to copy the results from Java.
nativeFromJava(mEnv, *config, key);
mEnv->DeleteLocalRef(key);
mEnv->DeleteLocalRef(uvMapArray);
if (stringLabel) {
mEnv->DeleteLocalRef(stringLabel);
}
if (materialInstance == nullptr) {
return nullptr;
}
return (MaterialInstance*) mEnv->CallLongMethod(materialInstance, mMaterialInstanceGetNativeObject);
}
const Material* const* getMaterials() const noexcept override {
jobjectArray javaMaterials = (jobjectArray) mEnv->CallObjectMethod(mJavaProvider, mGetMaterials);
const size_t count = mEnv->GetArrayLength(javaMaterials);
delete mPreviousMaterials;
using MaterialPointer = Material*;
mPreviousMaterials = new MaterialPointer[count];
// Call "getNativeObject" on each material in order to pass them up into the native layer.
for (size_t i = 0; i < count; ++i) {
jobject javaMaterial = mEnv->GetObjectArrayElement(javaMaterials, i);
jlong matPointer = mEnv->CallLongMethod(javaMaterial, mMaterialGetNativeObject);
mPreviousMaterials[i] = (Material*) matPointer;
}
return mPreviousMaterials;
}
size_t getMaterialsCount() const noexcept override {
jobjectArray javaMaterials = (jobjectArray) mEnv->CallObjectMethod(mJavaProvider, mGetMaterials);
return mEnv->GetArrayLength(javaMaterials);
}
void destroyMaterials() override {
mEnv->CallObjectMethod(mJavaProvider, mDestroyMaterials);
}
bool needsDummyData(VertexAttribute attrib) const noexcept override {
return mEnv->CallBooleanMethod(mJavaProvider, mNeedsDummyData, (int) attrib);
}
};
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_gltfio_AssetLoader_nCreateAssetLoader(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeProvider, jlong nativeEntities) {
Java_com_google_android_filament_gltfio_AssetLoader_nCreateAssetLoader(JNIEnv* env, jclass,
jlong nativeEngine, jobject provider, jlong nativeEntities) {
Engine* engine = (Engine*) nativeEngine;
MaterialProvider* materials = (MaterialProvider*) nativeProvider;
MaterialProvider* materials = new JavaMaterialProvider(env, provider);
EntityManager* entities = (EntityManager*) nativeEntities;
NameComponentManager* names = new NameComponentManager(*entities);
return (jlong) AssetLoader::create({engine, materials, names, entities});
@@ -44,6 +177,7 @@ extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_AssetLoader_nDestroyAssetLoader(JNIEnv*, jclass,
jlong nativeLoader) {
AssetLoader* loader = (AssetLoader*) nativeLoader;
delete loader->getMaterialProvider();
NameComponentManager* names = loader->getNames();
AssetLoader::destroy(&loader);
delete names;