Settings UI: LightManager ShadowOptions now affect all lights.

This commit is contained in:
Philip Rideout
2021-06-29 09:00:17 -07:00
parent fcca62ec6a
commit 86461663d8
10 changed files with 200 additions and 103 deletions

View File

@@ -84,7 +84,13 @@ Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclas
}
}
AutomationEngine* automation = (AutomationEngine*) nativeAutomation;
automation->tick((View*) view, ptrMaterials, materialCount, (Renderer*) renderer, deltaTime);
AutomationEngine::ViewerContent content = {
.view = (View*) view,
.renderer = (Renderer*) renderer,
.materials = ptrMaterials,
.materialCount = (size_t) materialCount,
};
automation->tick(content, deltaTime);
if (longMaterials) {
env->ReleaseLongArrayElements(materials, longMaterials, 0);
delete[] ptrMaterials;
@@ -94,8 +100,9 @@ Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclas
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_utils_AutomationEngine_nApplySettings(JNIEnv* env, jclass klass,
jlong nativeAutomation, jstring json, jlong view, jlongArray materials, jlong nativeIbl,
jint lightEntity, jlong nativeLm, jlong scene, jlong renderer) {
jint sunlightEntity, jintArray assetLights, jlong nativeLm, jlong scene, jlong renderer) {
using MaterialPointer = MaterialInstance*;
jsize materialCount = 0;
jlong* longMaterials = nullptr;
MaterialPointer* ptrMaterials = nullptr;
@@ -107,17 +114,42 @@ Java_com_google_android_filament_utils_AutomationEngine_nApplySettings(JNIEnv* e
ptrMaterials[i] = (MaterialPointer) longMaterials[i];
}
}
jsize lightCount = 0;
jint* intLights = nullptr;
if (assetLights) {
lightCount = env->GetArrayLength(assetLights);
intLights = env->GetIntArrayElements(assetLights, nullptr);
}
static_assert(sizeof(jint) == sizeof(Entity));
AutomationEngine* automation = (AutomationEngine*) nativeAutomation;
const char* nativeJson = env->GetStringUTFChars(json, 0);
size_t jsonLength = env->GetStringUTFLength(json);
automation->applySettings(nativeJson, jsonLength, (View*) view, ptrMaterials, materialCount,
(IndirectLight*) nativeIbl, (Entity&) lightEntity, (LightManager*) nativeLm,
(Scene*) scene, (Renderer*) renderer);
AutomationEngine::ViewerContent content = {
.view = (View*) view,
.renderer = (Renderer*) renderer,
.materials = ptrMaterials,
.materialCount = (size_t) materialCount,
.lightManager = (LightManager*) nativeLm,
.scene = (Scene*) scene,
.indirectLight = (IndirectLight*) nativeIbl,
.sunlight = (Entity&) sunlightEntity,
.assetLights = (Entity*) intLights,
.assetLightCount = (size_t) lightCount,
};
automation->applySettings(nativeJson, jsonLength, content);
env->ReleaseStringUTFChars(json, nativeJson);
if (longMaterials) {
env->ReleaseLongArrayElements(materials, longMaterials, 0);
delete[] ptrMaterials;
}
if (intLights) {
env->ReleaseIntArrayElements(assetLights, intLights, 0);
}
}
extern "C" JNIEXPORT void JNICALL

View File

@@ -51,7 +51,7 @@ import com.google.android.filament.Renderer;
* (shouldClose) that is triggered after the last test has been invoked.
*/
public class AutomationEngine {
private long mNativeObject;
private final long mNativeObject;
private ColorGrading mColorGrading;
/**
@@ -76,6 +76,20 @@ public class AutomationEngine {
public boolean verbose = true;
}
/**
* Collection of Filament objects that can be modified by the automation engine.
*/
public static class ViewerContent {
public View view;
public Renderer renderer;
public MaterialInstance[] materials;
public LightManager lightManager;
public Scene scene;
public IndirectLight indirectLight;
public @Entity int sunlight;
public @Entity int[] assetLights;
}
/**
* Allows remote control for the viewer.
*/
@@ -141,22 +155,22 @@ public class AutomationEngine {
* This is when settings get applied, screenshots are (optionally) exported, and the internal
* test counter is potentially incremented.
*
* @param view The Filament View that automation pushes changes to.
* @param materials Optional set of of materials that can receive parameter tweaks.
* @param renderer The Filament Renderer that can be used to take screenshots.
* @param content Contains the Filament View, Materials, and Renderer that get modified.
* @param deltaTime The amount of time that has passed since the previous tick in seconds.
*/
public void tick(@NonNull View view, @Nullable MaterialInstance[] materials,
@NonNull Renderer renderer, float deltaTime) {
public void tick(@NonNull ViewerContent content, float deltaTime) {
if (content.view == null || content.renderer == null) {
throw new IllegalStateException("Must provide a View and Renderer");
}
long[] nativeMaterialInstances = null;
if (materials != null) {
nativeMaterialInstances = new long[materials.length];
if (content.materials != null) {
nativeMaterialInstances = new long[content.materials.length];
for (int i = 0; i < nativeMaterialInstances.length; i++) {
nativeMaterialInstances[i] = materials[i].getNativeObject();
nativeMaterialInstances[i] = content.materials[i].getNativeObject();
}
}
long nativeView = view.getNativeObject();
long nativeRenderer = renderer.getNativeObject();
long nativeView = content.view.getNativeObject();
long nativeRenderer = content.renderer.getNativeObject();
nTick(mNativeObject, nativeView, nativeMaterialInstances, nativeRenderer, deltaTime);
}
@@ -168,24 +182,32 @@ public class AutomationEngine {
*
* This updates the stashed Settings object, then pushes those settings to the given
* Filament objects. Clients can optionally call getColorGrading() after calling this method.
*
* @param settingsJson Contains the JSON string with a set of changes that need to be pushed.
* @param content Contains a set of Filament objects that you want to mutate.
*/
public void applySettings(@NonNull String settingsJson, @NonNull View view,
@Nullable MaterialInstance[] materials, @Nullable IndirectLight ibl, @Entity int light,
@NonNull LightManager lm, @NonNull Scene scene, @NonNull Renderer renderer) {
public void applySettings(@NonNull String settingsJson, @NonNull ViewerContent content) {
if (content.view == null || content.renderer == null) {
throw new IllegalStateException("Must provide a View and Renderer");
}
long[] nativeMaterialInstances = null;
if (materials != null) {
nativeMaterialInstances = new long[materials.length];
if (content.lightManager == null || content.scene == null) {
throw new IllegalStateException("Must provide a LightManager and Scene");
}
if (content.materials != null) {
nativeMaterialInstances = new long[content.materials.length];
for (int i = 0; i < nativeMaterialInstances.length; i++) {
nativeMaterialInstances[i] = materials[i].getNativeObject();
nativeMaterialInstances[i] = content.materials[i].getNativeObject();
}
}
long nativeView = view.getNativeObject();
long nativeIbl = ibl == null ? 0 : ibl.getNativeObject();
long nativeLm = lm.getNativeObject();
long nativeScene = scene.getNativeObject();
long nativeRenderer = renderer.getNativeObject();
long nativeView = content.view.getNativeObject();
long nativeIbl = content.indirectLight == null ? 0 : content.indirectLight.getNativeObject();
long nativeLm = content.lightManager.getNativeObject();
long nativeScene = content.scene.getNativeObject();
long nativeRenderer = content.renderer.getNativeObject();
nApplySettings(mNativeObject, settingsJson, nativeView, nativeMaterialInstances,
nativeIbl, light, nativeLm, nativeScene, nativeRenderer);
nativeIbl, content.sunlight, content.assetLights, nativeLm, nativeScene,
nativeRenderer);
}
/**
@@ -247,7 +269,8 @@ public class AutomationEngine {
private static native void nTick(long nativeObject, long view, long[] materials, long renderer,
float deltaTime);
private static native void nApplySettings(long nativeObject, String jsonSettings, long view,
long[] materials, long ibl, int light, long lightManager, long scene, long renderer);
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
long scene, long renderer);
private static native void nGetViewerOptions(long nativeObject, Object result);
private static native long nGetColorGrading(long nativeObject, long nativeEngine);
private static native void nSignalBatchMode(long nativeObject);

View File

@@ -62,6 +62,7 @@ class MainActivity : Activity() {
private val automation = AutomationEngine()
private var loadStartTime = 0L
private var loadStartFence: Fence? = null
private val viewerContent = AutomationEngine.ViewerContent()
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
@@ -76,6 +77,12 @@ class MainActivity : Activity() {
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
modelViewer = ModelViewer(surfaceView)
viewerContent.view = modelViewer.view
viewerContent.indirectLight = modelViewer.scene.indirectLight
viewerContent.sunlight = modelViewer.light
viewerContent.lightManager = modelViewer.engine.lightManager
viewerContent.scene = modelViewer.scene
viewerContent.renderer = modelViewer.renderer
surfaceView.setOnTouchListener { _, event ->
modelViewer.onTouchEvent(event)
@@ -316,9 +323,8 @@ class MainActivity : Activity() {
fun loadSettings(message: RemoteServer.ReceivedMessage) {
val json = StandardCharsets.UTF_8.decode(message.buffer).toString()
automation.applySettings(json, modelViewer.view, null,
modelViewer.scene.indirectLight, modelViewer.light, modelViewer.engine.lightManager,
modelViewer.scene, modelViewer.renderer)
viewerContent.assetLights = modelViewer.asset?.lightEntities
automation.applySettings(json, viewerContent)
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
modelViewer.cameraFocalLength = automation.viewerOptions.cameraFocalLength
updateRootTransform()

View File

@@ -152,9 +152,17 @@ using namespace utils;
}
- (void)loadSettings:(viewer::ReceivedMessage const*)message {
_automation->applySettings(message->buffer, message->bufferByteCount, self.modelView.view,
nullptr, 0u, _indirectLight, _sun, &self.modelView.engine->getLightManager(),
self.modelView.scene, self.modelView.renderer);
viewer::AutomationEngine::ViewerContent content = {
.view = self.modelView.view,
.renderer = self.modelView.renderer,
.materials = nullptr,
.materialCount = 0u,
.lightManager = &self.modelView.engine->getLightManager(),
.scene = self.modelView.scene,
.indirectLight = _indirectLight,
.sunlight = _sun,
};
_automation->applySettings(message->buffer, message->bufferByteCount, content);
ColorGrading* const colorGrading = _automation->getColorGrading(self.modelView.engine);
self.modelView.view->setColorGrading(colorGrading);
self.modelView.cameraFocalLength = _automation->getViewerOptions().cameraFocalLength;

View File

@@ -84,6 +84,22 @@ public:
bool exportSettings = false;
};
/**
* Collection of Filament objects that can be modified by the automation engine.
*/
struct ViewerContent {
View* view;
Renderer* renderer;
MaterialInstance* const* materials;
size_t materialCount;
LightManager* lightManager;
Scene* scene;
IndirectLight* indirectLight;
utils::Entity sunlight;
utils::Entity* assetLights;
size_t assetLightCount;
};
/**
* Creates an automation engine and places it in an idle state.
*
@@ -139,14 +155,10 @@ public:
* This is when settings get applied, screenshots are (optionally) exported, and the internal
* test counter is potentially incremented.
*
* @param view The Filament View that automation pushes changes to.
* @param materials An optional set of of materials that can receive parameter tweaks.
* @param materialCount The number of items in the materials array.
* @param renderer The Filament Renderer that can be used to take screenshots.
* @param content Contains the Filament View, Materials, and Renderer that get modified.
* @param deltaTime The amount of time that has passed since the previous tick in seconds.
*/
void tick(View* view, MaterialInstance* const* materials, size_t materialCount,
Renderer* renderer, float deltaTime);
void tick(const ViewerContent& content, float deltaTime);
/**
* Mutates a set of client-owned Filament objects according to a JSON string.
@@ -156,10 +168,12 @@ public:
*
* This updates the stashed Settings object, then pushes those settings to the given
* Filament objects. Clients can optionally call getColorGrading() after calling this method.
*
* @param json Contains the JSON string with a set of changes that need to be pushed.
* @param jsonLength Number of characters in the json string.
* @param content Contains a set of Filament objects that you want to mutate.
*/
void applySettings(const char* json, size_t jsonLength, View* view,
MaterialInstance* const* materials, size_t materialCount, IndirectLight* ibl,
utils::Entity sunlight, LightManager* lm, Scene* scene, Renderer* renderer);
void applySettings(const char* json, size_t jsonLength, const ViewerContent& content);
/**
* Gets a color grading object that corresponds to the latest settings.

View File

@@ -65,7 +65,7 @@ using LightManager = filament::LightManager;
void applySettings(const ViewSettings& settings, View* dest);
void applySettings(const MaterialSettings& settings, MaterialInstance* dest);
void applySettings(const LightSettings& settings, IndirectLight* ibl, utils::Entity sunlight,
LightManager* lm, Scene* scene);
utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene);
void applySettings(const ViewerOptions& settings, Camera* camera, Skybox* skybox,
Renderer* renderer);

View File

@@ -137,23 +137,23 @@ void AutomationEngine::exportSettings(const Settings& settings, const char* file
gStatus = "Exported to '" + std::string(filename) + "' in the current folder.";
}
void AutomationEngine::applySettings(const char* json, size_t jsonLength, View* view,
MaterialInstance* const* materials, size_t materialCount, IndirectLight* ibl,
utils::Entity sunlight, LightManager* lm, Scene* scene, Renderer* renderer) {
void AutomationEngine::applySettings(const char* json, size_t jsonLength,
const ViewerContent& content) {
JsonSerializer serializer;
if (!serializer.readJson(json, jsonLength, mSettings)) {
std::string jsonWithTerminator(json, json + jsonLength);
slog.e << "Badly formed JSON:\n" << jsonWithTerminator.c_str() << io::endl;
return;
}
viewer::applySettings(mSettings->view, view);
for (size_t i = 0; i < materialCount; i++) {
viewer::applySettings(mSettings->material, materials[i]);
viewer::applySettings(mSettings->view, content.view);
for (size_t i = 0; i < content.materialCount; i++) {
viewer::applySettings(mSettings->material, content.materials[i]);
}
viewer::applySettings(mSettings->lighting, ibl, sunlight, lm, scene);
Camera* camera = &view->getCamera();
Skybox* skybox = scene->getSkybox();
viewer::applySettings(mSettings->viewer, camera, skybox, renderer);
viewer::applySettings(mSettings->lighting, content.indirectLight, content.sunlight,
content.assetLights, content.assetLightCount, content.lightManager, content.scene);
Camera* camera = &content.view->getCamera();
Skybox* skybox = content.scene->getSkybox();
viewer::applySettings(mSettings->viewer, camera, skybox, content.renderer);
}
ColorGrading* AutomationEngine::getColorGrading(Engine* engine) {
@@ -180,15 +180,14 @@ ViewerOptions AutomationEngine::getViewerOptions() const {
return mSettings->viewer;
}
void AutomationEngine::tick(View* view, MaterialInstance* const* materials, size_t materialCount,
Renderer* renderer, float deltaTime) {
const auto activateTest = [this, view, materials, materialCount]() {
void AutomationEngine::tick(const ViewerContent& content, float deltaTime) {
const auto activateTest = [this, content]() {
mElapsedTime = 0;
mElapsedFrames = 0;
mSpec->get(mCurrentTest, mSettings);
viewer::applySettings(mSettings->view, view);
for (size_t i = 0; i < materialCount; i++) {
viewer::applySettings(mSettings->material, materials[i]);
viewer::applySettings(mSettings->view, content.view);
for (size_t i = 0; i < content.materialCount; i++) {
viewer::applySettings(mSettings->material, content.materials[i]);
}
if (mOptions.verbose) {
utils::slog.i << "Running test " << mCurrentTest << utils::io::endl;
@@ -228,7 +227,7 @@ void AutomationEngine::tick(View* view, MaterialInstance* const* materials, size
}
if (mOptions.exportScreenshots) {
exportScreenshot(view, renderer, prefix + ".ppm", isLastTest, this);
exportScreenshot(content.view, content.renderer, prefix + ".ppm", isLastTest, this);
}
if (isLastTest) {

View File

@@ -906,7 +906,7 @@ void applySettings(const MaterialSettings& settings, MaterialInstance* dest) {
}
void applySettings(const LightSettings& settings, IndirectLight* ibl, utils::Entity sunlight,
LightManager* lm, Scene* scene) {
utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene) {
auto light = lm->getInstance(sunlight);
if (light) {
if (settings.enableSunlight) {
@@ -924,6 +924,13 @@ void applySettings(const LightSettings& settings, IndirectLight* ibl, utils::Ent
ibl->setIntensity(settings.iblIntensity);
ibl->setRotation(math::mat3f::rotation(settings.iblRotation, math::float3 { 0, 1, 0 }));
}
for (size_t i = 0; i < sceneLightCount; i++) {
auto light = lm->getInstance(sceneLights[i]);
if (lm->isSpotLight(light)) {
lm->setShadowCaster(light, settings.enableShadows);
}
lm->setShadowOptions(light, settings.shadowOptions);
}
}
static LinearColor inverseTonemapSRGB(sRGBColor x) {

View File

@@ -669,48 +669,54 @@ void SimpleViewer::updateUserInterface() {
auto& light = mSettings.lighting;
if (ImGui::CollapsingHeader("Light")) {
ImGui::Indent();
ImGui::SliderFloat("IBL intensity", &light.iblIntensity, 0.0f, 100000.0f);
ImGui::SliderAngle("IBL rotation", &light.iblRotation);
ImGui::SliderFloat("Sun intensity", &light.sunlightIntensity, 50000.0, 150000.0f);
ImGuiExt::DirectionWidget("Sun direction", light.sunlightDirection.v);
ImGui::Checkbox("Enable sunlight", &light.enableSunlight);
ImGui::Checkbox("Enable shadows", &light.enableShadows);
int mapSize = light.shadowOptions.mapSize;
ImGui::SliderInt("Shadow map size", &mapSize, 32, 1024);
light.shadowOptions.mapSize = mapSize;
if (ImGui::CollapsingHeader("Indirect light")) {
ImGui::SliderFloat("IBL intensity", &light.iblIntensity, 0.0f, 100000.0f);
ImGui::SliderAngle("IBL rotation", &light.iblRotation);
}
if (ImGui::CollapsingHeader("Sunlight")) {
ImGui::Checkbox("Enable sunlight", &light.enableSunlight);
ImGui::SliderFloat("Sun intensity", &light.sunlightIntensity, 50000.0, 150000.0f);
ImGuiExt::DirectionWidget("Sun direction", light.sunlightDirection.v);
}
if (ImGui::CollapsingHeader("All lights")) {
ImGui::Checkbox("Enable shadows", &light.enableShadows);
int mapSize = light.shadowOptions.mapSize;
ImGui::SliderInt("Shadow map size", &mapSize, 32, 1024);
light.shadowOptions.mapSize = mapSize;
bool enableVsm = mSettings.view.shadowType == ShadowType::VSM;
ImGui::Checkbox("Enable VSM", &enableVsm);
mSettings.view.shadowType = enableVsm ? ShadowType::VSM : ShadowType::PCF;
bool enableVsm = mSettings.view.shadowType == ShadowType::VSM;
ImGui::Checkbox("Enable VSM", &enableVsm);
mSettings.view.shadowType = enableVsm ? ShadowType::VSM : ShadowType::PCF;
char label[32];
snprintf(label, 32, "%d", 1 << mVsmMsaaSamplesLog2);
ImGui::SliderInt("VSM MSAA samples", &mVsmMsaaSamplesLog2, 0, 3, label);
light.shadowOptions.vsm.msaaSamples = static_cast<uint8_t>(1u << mVsmMsaaSamplesLog2);
char label[32];
snprintf(label, 32, "%d", 1 << mVsmMsaaSamplesLog2);
ImGui::SliderInt("VSM MSAA samples", &mVsmMsaaSamplesLog2, 0, 3, label);
light.shadowOptions.vsm.msaaSamples = static_cast<uint8_t>(1u << mVsmMsaaSamplesLog2);
int vsmAnisotropy = mSettings.view.vsmShadowOptions.anisotropy;
snprintf(label, 32, "%d", 1 << vsmAnisotropy);
ImGui::SliderInt("VSM anisotropy", &vsmAnisotropy, 0, 3, label);
mSettings.view.vsmShadowOptions.anisotropy = vsmAnisotropy;
ImGui::Checkbox("VSM mipmapping", &mSettings.view.vsmShadowOptions.mipmapping);
ImGui::SliderFloat("VSM blur", &light.shadowOptions.vsm.blurWidth, 0.0f, 125.0f);
int vsmAnisotropy = mSettings.view.vsmShadowOptions.anisotropy;
snprintf(label, 32, "%d", 1 << vsmAnisotropy);
ImGui::SliderInt("VSM anisotropy", &vsmAnisotropy, 0, 3, label);
mSettings.view.vsmShadowOptions.anisotropy = vsmAnisotropy;
ImGui::Checkbox("VSM mipmapping", &mSettings.view.vsmShadowOptions.mipmapping);
ImGui::SliderFloat("VSM blur", &light.shadowOptions.vsm.blurWidth, 0.0f, 125.0f);
// These are not very useful in practice (defaults are good), but we keep them here for debugging
//ImGui::SliderFloat("VSM exponent", &mSettings.view.vsmShadowOptions.exponent, 0.0, 6.0f);
//ImGui::SliderFloat("VSM Light bleed", &mSettings.view.vsmShadowOptions.lightBleedReduction, 0.0, 1.0f);
//ImGui::SliderFloat("VSM min variance scale", &mSettings.view.vsmShadowOptions.minVarianceScale, 0.0, 10.0f);
// These are not very useful in practice (defaults are good), but we keep them here for debugging
//ImGui::SliderFloat("VSM exponent", &mSettings.view.vsmShadowOptions.exponent, 0.0, 6.0f);
//ImGui::SliderFloat("VSM Light bleed", &mSettings.view.vsmShadowOptions.lightBleedReduction, 0.0, 1.0f);
//ImGui::SliderFloat("VSM min variance scale", &mSettings.view.vsmShadowOptions.minVarianceScale, 0.0, 10.0f);
int shadowCascades = light.shadowOptions.shadowCascades;
ImGui::SliderInt("Cascades", &shadowCascades, 1, 4);
ImGui::Checkbox("Debug cascades",
debug.getPropertyAddress<bool>("d.shadowmap.visualize_cascades"));
ImGui::Checkbox("Enable contact shadows", &light.shadowOptions.screenSpaceContactShadows);
ImGui::SliderFloat("Split pos 0", &light.shadowOptions.cascadeSplitPositions[0], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 1", &light.shadowOptions.cascadeSplitPositions[1], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 2", &light.shadowOptions.cascadeSplitPositions[2], 0.0f, 1.0f);
int shadowCascades = light.shadowOptions.shadowCascades;
ImGui::SliderInt("Cascades", &shadowCascades, 1, 4);
ImGui::Checkbox("Debug cascades",
debug.getPropertyAddress<bool>("d.shadowmap.visualize_cascades"));
ImGui::Checkbox("Enable contact shadows", &light.shadowOptions.screenSpaceContactShadows);
ImGui::SliderFloat("Split pos 0", &light.shadowOptions.cascadeSplitPositions[0], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 1", &light.shadowOptions.cascadeSplitPositions[1], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 2", &light.shadowOptions.cascadeSplitPositions[2], 0.0f, 1.0f);
light.shadowOptions.shadowCascades = shadowCascades;
}
ImGui::Unindent();
light.shadowOptions.shadowCascades = shadowCascades;
}
if (ImGui::CollapsingHeader("Fog")) {

View File

@@ -713,11 +713,13 @@ int main(int argc, char** argv) {
FilamentApp::get().close();
return;
}
Settings* settings = &app.viewer->getSettings();
MaterialInstance* const* materials = app.asset->getMaterialInstances();
size_t materialCount = app.asset->getMaterialInstanceCount();
app.automationEngine->tick(view, materials, materialCount, renderer,
ImGui::GetIO().DeltaTime);
AutomationEngine::ViewerContent content = {
.view = view,
.renderer = renderer,
.materials = app.asset->getMaterialInstances(),
.materialCount = app.asset->getMaterialInstanceCount(),
};
app.automationEngine->tick(content, ImGui::GetIO().DeltaTime);
};
FilamentApp& filamentApp = FilamentApp::get();